1 Introduction

This R notebook implements the analysis of the Record-seq and RNA-seq readout of the transient diet experiments described in ‘Transcriptional Recordings in the gut’ manuscript. The following files need to be stored in the directory data within the working directory:

transient-diet/
    secondaryAnalysis.Rmd
    data/
        transientDiet1_Recordseq_genomemapping.txt
        transientDiet1_Recordseq_designmatrix.txt
        transientDiet1_RNAseq_genomemapping.txt
        transientDiet1_RNAseq_designmatrix.txt
        transientDiet2_Recordseq_genomemapping.txt
        transientDiet2_Recordseq_plasmidmapping.txt
        transientDiet2_Recordseq_designmatrix.txt
        transientDiet2_RNAseq_genomemapping.txt
        transientDiet2_RNAseq_designmatrix.txt
        Chow.Fat.pathway.txt
        Chow.Starch.pathway.txt
        Fat.Starch.pathway.txt
    

2 Libraries

The recoRdseq package and dependencies are required for this analysis, and the fantastic patchwork package is used for visualization.

if(!require(devtools)){
  install.packages("devtools")
}
library(devtools)
if(!require(eulerr)){
  install.packages("eulerr")
}
library(eulerr)
if(!require(plyr)){
  install.packages("plyr")
}
library(plyr)
if(!require(recoRdseq)){
  install_github("plattlab/Transcriptional-Recording", subdir="recoRdseq")
}
if(!require(factoextra)){
  install.packages("factoextra")
}
library(factoextra)
if(!require(patchwork)){
  install.packages('patchwork')
}
if(!require(ggrepel)){
  install.packages('ggrepel')
}
library(recoRdseq)
library(patchwork)
library(stringr)
library(ggrepel)
colour_code = list(
  Diet = c(Chow = "#538bce", Fat="#ed915c", Starch='#42bb7f')) # we set a consistent color scheme for the three diet groups

theme_pub<-theme_minimal()+
  theme(legend.position="bottom", legend.justification="center", legend.margin=margin(0,0,0,0),legend.box.margin=margin(-10,-10,-10,-10),plot.title = element_text(hjust = 0.5), legend.spacing.y =  unit(0, 'mm'), legend.box='vertical', legend.key.size = unit(0.1, "cm"),legend.key.width = unit(0.1,"cm"), legend.text=element_text(size=5), text = element_text(size=5), panel.grid.minor = element_blank(), axis.text = element_text(size=5, colour='black'), panel.grid.major = element_line(size = 0.24, colour='gray1', linetype = 2)) # we set a consistent theme for ggplot objects

custom.config = umap.defaults
custom.config$random_state = 2

3 Transient Diet 1

Data for the transient diet experiment with 14 days is analyzed first.

3.1 Importing and pre-processing data for transient diet 1

We import the data matrices for both Record-seq and RNA-seq, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data.

rec1<-as.data.frame(read.table("data/transientDiet1_Recordseq_genomealigning.txt", header = TRUE))
rec1d<-as.data.frame(read.table("data/transientDiet1_Recordseq_designmatrix.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec1, rec1d, minCountsPerSample = 10000)
rec1<-DEList[[1]]
rec1d<-DEList[[2]]
rec1d<-rec1d[rec1d$Day>1,]
rec1<-rec1[,rownames(rec1d)]
rec1_tf<-recoRdseq.transform(rec1, rec1d,transformation = 'vst')

rna1<-as.data.frame(read.table("data/transientDiet1_RNAseq_genomealigning.txt", header = TRUE))
rna1d<-as.data.frame(read.table("data/transientDiet1_RNAseq_designmatrix.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rna1, rna1d, minCountsPerSample = 100000)
rna1<-DEList[[1]]
rna1d<-DEList[[2]]
rna1_tf<-recoRdseq.transform(rna1, rna1d)
rnadays<-unique(rna1d$Day)
rna1_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_genomealigning.txt", header = TRUE))
rna1d_day14<-as.data.frame(read.table("data/transientDiet1_day14_RNAseq_designmatrix.txt", header = TRUE))
rna1d_day14<-rna1d_day14[,1:3]
DEList<-recoRdseq.preprocess(rna1_day14, rna1d_day14, minCountsPerSample = 100000)
rna1_day14<-DEList[[1]]
rna1d_day14<-DEList[[2]]
rna1_day14tf<-recoRdseq.transform(rna1_day14, rna1d_day14, transformation = 'vst')

3.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

For a comparison between Record-seq and RNA-seq, we check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)

Next, we check if Record-seq or RNA-seq can distinguish the samples on day 14 (7 days after switching all samples to Chow diet).

3.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec1.deseq<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='DESeq2')
rec1.edger<-recoRdseq.DE(rec1_day7, rec1d_day7, tool='edgeR')
rec1.deseq.genes<-recoRdseq.filterDEG(rec1.deseq, p = 0.05)
rec1.edger.genes<-recoRdseq.filterDEG(rec1.edger, p = 0.05)
rec1.DEG<-rec1.deseq[intersect(rec1.deseq.genes, rec1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.deseq))))]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rec1.DEG<-rec1.DEG[order(rec1.DEG$padj),]
rec1.DEG$geneID<-as.character(rec1.DEG$geneID)
rna1.deseq<-recoRdseq.DE(rna1, rna1d, tool='DESeq2')
rna1.edger<-recoRdseq.DE(rna1, rna1d, tool='edgeR')
rna1.deseq.genes<-recoRdseq.filterDEG(rna1.deseq, p = 0.05)
rna1.edger.genes<-recoRdseq.filterDEG(rna1.edger, p = 0.05)
rna1.DEG<-rna1.deseq[intersect(rna1.deseq.genes, rna1.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna1.deseq))))]
rna1.DEG$geneID<-as.character(rna1.DEG$geneID)

rec1.novel<-rec1.DEG[-which(rec1.DEG$geneID%in%rna1.DEG$geneID),]

For Record-seq, we also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec1.DEG.list<-list()
rec1.er<-list()
rec1.de<-list()
rec1.global.DEG<-c()
for(i in unique(rec1d$Day)){
  if(i<8){
    dt<-rec1d[which(rec1d$Day==i), 1, drop=FALSE]
    de<-rec1[,which(colnames(rec1)%in%rownames(dt))]
    rec1.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec1.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec1.de.genes<-recoRdseq.filterDEG(rec1.de[[i]], p = 0.1)
    rec1.er.genes<-recoRdseq.filterDEG(rec1.er[[i]], p = 0.1)
    rec1.DEG.list[[i]]<-rec1.de[[i]][intersect(rec1.de.genes, rec1.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec1.de[[i]]))))]
    rec1.global.DEG<- c(rec1.global.DEG, as.character(intersect(rec1.de.genes,rec1.er.genes)))
  }
}
rec1.global.DEG1<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
rec1.global.DEG2<-as.data.frame(table(rec1.global.DEG)[order(table(rec1.global.DEG), decreasing = TRUE)])
colnames(rec1.global.DEG1)<-c("geneID", "days_DE")
colnames(rec1.global.DEG2)<-c("geneID", "days_DE")
rec1.global.DEG1$geneID<-as.character(rec1.global.DEG1$geneID)
rec1.global.DEG2$geneID<-as.character(rec1.global.DEG2$geneID)
for(i in unique(rec1d$Day)){
  if(i<8){
  rec1.global.DEG1$V1<-rec1.de[[i]][rec1.global.DEG1$geneID,4]
  colnames(rec1.global.DEG1)[ncol(rec1.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec1.global.DEG2$V1<-rec1.de[[i]][rec1.global.DEG2$geneID,8]
  colnames(rec1.global.DEG2)[ncol(rec1.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec1.global.DEG1$log2FoldChange.max<-rec1.global.DEG1[,3:ncol(rec1.global.DEG1)][cbind(1:nrow(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), max.col(replace(x <- abs(rec1.global.DEG1[,3:ncol(rec1.global.DEG1)]), is.na(x), -Inf)))]
rec1.global.DEG2$log2FoldChange.max<-rec1.global.DEG2[,3:ncol(rec1.global.DEG2)][cbind(1:nrow(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), max.col(replace(x <- abs(rec1.global.DEG2[,3:ncol(rec1.global.DEG2)]), is.na(x), -Inf)))]

rec1.global.DEG<-rec1.global.DEG1[,c(1,2,ncol(rec1.global.DEG1))]
rec1.global.DEG$V1<-rec1.global.DEG2[,ncol(rec1.global.DEG1)]
colnames(rec1.global.DEG)[3]<-"log2FoldChange.max_FC"
colnames(rec1.global.DEG)[4]<-"log2FoldChange.max_SC"

3.4 Plotting individual DEGs:

We plot vst-transformed genome-mapping spacer counts for 6 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec1d_day7[rec1d_day7$Diet!='Fat',]
dt<-rec1_day7tf[gntR_genes, rownames(de)]
rec1.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec1.gntR.plot.df<-melt(rec1.gntR.plot.df, id.vars = 'Diet')
rec1.gntR.plot<-ggplot(rec1.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-aligning spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec1.gntR.plot+plot_annotation()

NA
NA

3.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rownames(rec1.DEG)), grep("rrl", rownames(rec1.DEG)))
if(length(ribosomal)>0){
  rec1.DEG<-rec1.DEG[-ribosomal,]
}
dheatmap<-as.data.frame(t(apply(rec1_day7tf[rec1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec1.day7<-pheatmap(dheatmap, annotation_col = rec1d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, clustering_distance_cols = "canberra", treeheight_col = 5,show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

ribosomal<-c(grep("rrs", rownames(rna1.DEG)), grep("rrl", rownames(rna1.DEG)))
if(length(ribosomal)>0){
  rna1.DEG<-rna1.DEG[-ribosomal,]
}
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna1_tf[rna1.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna1.day7<-pheatmap(dheatmap, annotation_col = rna1d[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

3.6 Volcano plots for Record-seq DEGs

We perform pairwise DE analysis using DESeq2 and edgeR to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

levels<-sort(unique(rec1d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec1.de.vals<-list()
rec1.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec1d_day7[which(rec1d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec1_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec1.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec1.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec1.de.vals[[i]]) <- rec1.de.vals[[i]]$geneID
  rownames(rec1.ed.vals[[i]]) <- rec1.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec1.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec1.de.vals[[i]]<-rec1.de.vals[[i]][complete.cases(rec1.de.vals[[i]]),]
  rec1.de.vals[[i]]$Group<-'None'
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange>1.5&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec1.de.vals[[i]]$Group[ which(rec1.de.vals[[i]]$log2FoldChange<(-1.5)&rec1.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec1.de.vals[[i]]$Group<-factor(rec1.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec1.de.vals[[i]]$label<-FALSE
  m1<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec1.de.vals[[i]][rec1.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec1.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec1.de.vals[[i]]$log2FoldChange[j])>1.5&rec1.de.vals[[i]]$padj[j]<0.1){
      rec1.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec1.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec1.de.vals[[i]][which(rec1.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

3.7 Clustering on final day of experiment

We want to check whether information about diet groups prior to switch can be retrieved at day 14 - i.e 7 days after the switch. For this, we use diet-signature genes identified before the switch to hierarchically cluster the groups. Diet-signature genes are defined here as the top 10 genes by number of days (Record-seq) or p-adj value (RNA-seq) detected as enriched (log2FC > 2.5) prior to the switch. We can perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

ribosomal<-c(grep("rrs", rec1.global.DEG$geneID), grep("rrl", rec1.global.DEG$geneID))
if(length(ribosomal)>0){
  rec1.global.DEG<-rec1.global.DEG[-ribosomal,]
}
geneShortList<-unique(c(rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec1.global.DEG[which(rec1.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec1.global.DEG[which(rowMeans(rec1.global.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec1_day14tf[geneShortList,], 1, zscorestandardize)))
heatmap.rec1<-pheatmap(dheatmap, annotation_col = rec1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 14 based on DEGS detected on day 7')

heatmap.genelist<-rec1_day14tf[geneShortList,]
colnames(heatmap.genelist)<-rec1d_day14$Diet


cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
geneShortList<-unique(c(rna1.DEG[which(rna1.DEG$log2FoldChange.Fat_vs_Chow>2.5), 1][1:12], rna1.DEG[which(rna1.DEG$log2FoldChange.Starch_vs_Chow>2.5), 1][1:12],rna1.DEG[which(rowMeans(rna1.DEG[,3:4])<(-2.5)), 1][1:12]))
dheatmap<-as.data.frame(t(apply(rna1_day14tf[geneShortList,], 1, zscorestandardize)))
dheatmap<-dheatmap[complete.cases(dheatmap),]
heatmap.rna1<-pheatmap(dheatmap, annotation_col = rna1d_day14[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0,  treeheight_col=5, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 14 based on DEGS detected on day 7')

4 Transient Diet 2

We now analyze data for the extended transient diet experiment with 20 days.

4.1 Importing and pre-processing data for transient diet 2

We import the data matrices, filter them for lowly expressed genes as well as outlier samples with low cumulative counts, and use vst from DESeq2 to normalize and transform the data. We also exclude day1 from the analysis since we have emperically observed that the data are noisy for the first day of colonization.

rec2<-as.data.frame(read.table("data/transientDiet2_Recordseq_genomealigning.txt", header = TRUE))
rec2d<-as.data.frame(read.table("data/transientDiet2_Recordseq_designmatrix.txt", header = TRUE))
DEList<-recoRdseq.preprocess(rec2, rec2d, minCountsPerSample = 10000)
rec2<-DEList[[1]]
rec2d<-DEList[[2]]
rec2d<-rec2d[rec2d$Day>1,]
rec2<-rec2[,rownames(rec2d)]
rec2_tf<-recoRdseq.transform(rec2, rec2d)
rna2<-as.data.frame(read.table("data/transientDiet2_RNAseq_genomealigning.txt", header = TRUE))
rna2d<-as.data.frame(read.table("data/transientDiet2_RNAseq_designmatrix.txt", header = TRUE))
rna2d<-rna2d[,1:3]
DEList<-recoRdseq.preprocess(rna2, rna2d, minCountsPerSample = 100000)
rna2<-DEList[[1]]
rna2d<-DEList[[2]]
rna2_tf<-recoRdseq.transform(rna2, rna2d)
rnadays<-unique(rna2d$Day)

4.2 Data exploration

We use Principal Component analysis and UMAP for dimensionality reduction and exploring clusters in an unsupervised fashion in our data. We first generate these for the entire dataset from Record-seq:

rec2sds <- rowSds(as.matrix(rec2_tf))
o <- order(rec2sds, decreasing = TRUE)
rec2PCA<-prcomp(t(rec2_tf[o[1:500],]))
pca_stat<-summary(rec2PCA)
rec2.pca_variance<-pca_stat$importance[2,]
rec2PCA<-as.data.frame(rec2PCA$x)
rec2PCA$Diet<-rec2d$Diet
rec2PCA$Day<-factor(rec2d$Day, levels = 1:20)
rec2PCAplot<-ggplot(rec2PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(rec2.pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(rec2.pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data")

rec2UMAP<-umap(rec2PCA[,1:(ncol(rec2PCA)-2)], custom.config)
rec2UMAP<-as.data.frame(rec2UMAP$layout)
rec2UMAP$Day<-factor(rec2d$Day, levels = 1:20)
rec2UMAP$Diet<-rec2d$Diet
colnames(rec2UMAP)[1:2]<-c('UMAP1','UMAP2')
rec2UMAPplot<-ggplot(rec2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data (all days)")

rec2PCAplot+rec2UMAPplot+plot_annotation(tag_levels = 'A')

For a comparasion between Record-seq and RNA-seq, we exclude the days in Record-seq that don’t have corresponding RNA-seq data. We first check if the diet groups can be classified using PCA on day 7 (the last day when the mice are fed different diets, before switching all mice to a ‘Chow’ diet)


rec2d_day7<-rec2d[rec2d$Day==7,]
rec2_day7<-rec2[, rownames(rec2d_day7)]
rec2_day7tf<-recoRdseq.transform(rec2_day7, rec2d_day7)
rec2_day7_sds <- rowSds(as.matrix(rec2_day7tf))
o <- order(rec2_day7_sds, decreasing = TRUE)
rec2_day7PCA<-prcomp(t(rec2_day7tf[o[1:500],]))
pca_stat<-summary(rec2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rec2_day7PCA<-as.data.frame(rec2_day7PCA$x)
rec2_day7PCA$Diet<-rec2d_day7$Diet
rec2_day7PCA$Day<-factor(rec2d_day7$Day, levels = c(7))
rec2_day7PCAplot<-ggplot(rec2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of Record-seq data on day 7 ")

rna2d_day7<-rna2d[rna2d$Day==7,]
rna2_day7<-rna2[, rownames(rna2d_day7)]
rna2_day7tf<-recoRdseq.transform(rna2_day7, rna2d_day7)
rna2_day7_sds <- rowSds(as.matrix(rna2_day7tf))
o <- order(rna2_day7_sds, decreasing = TRUE)
rna2_day7PCA<-prcomp(t(rna2_day7tf[o[1:500],]))
pca_stat<-summary(rna2_day7PCA)
pca_variance<-pca_stat$importance[2,]
rna2_day7PCA<-as.data.frame(rna2_day7PCA$x)
rna2_day7PCA$Diet<-rna2d_day7$Diet
rna2_day7PCA$Day<-factor(rna2d_day7$Day, levels = c(7))
rna2_day7PCAplot<-ggplot(rna2_day7PCA, aes(x=PC1, y=PC2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(2,2.5))+geom_hline(yintercept = 0, size=0.24)+scale_fill_manual(values = as.vector(colour_code$Diet))+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+ylab(paste0("PC2 (", as.character(pca_variance[2]*100), "% variance explained)"))+xlab(paste0("PC1 (", as.character(pca_variance[1]*100), "% variance explained)"))+ggtitle(" PCA plot of RNA-seq data on day 7 ")


rec2_day7PCAplot+rna2_day7PCAplot+plot_annotation(tag_levels = 'A')

We then compare the temporal trajectories of Record-seq and RNA-seq using UMAPs. Record-seq data retains information about prior diet groups till the final day, and clusters strongly based on group; whereas RNA-seq data has a pronounced temporal change, and initial clusters for different diet groups quickly converge.

rec2d_rna<-rec2d[which(rec2d$Day%in%rnadays),]
rec2_rna<-rec2[, rownames(rec2d_rna)]
rec2_rnatf<-recoRdseq.transform(rec2_rna, rec2d_rna)
rec2rnasds <- rowSds(as.matrix(rec2_rnatf))
o <- order(rec2rnasds, decreasing = TRUE)
rec2rnaPCA<-prcomp(t(rec2_rnatf[o[1:500],]))
pca_stat<-summary(rec2rnaPCA)
rec2rna.pca_variance<-pca_stat$importance[2,]
rec2rnaPCA<-as.data.frame(rec2rnaPCA$x)
rec2rnaPCA$Diet<-rec2d_rna$Diet
rec2rnaPCA$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP<-umap(rec2rnaPCA[,1:(ncol(rec2rnaPCA)-2)], custom.config)
rec2rnaUMAP<-as.data.frame(rec2rnaUMAP$layout)
rec2rnaUMAP$Day<-factor(rec2d_rna$Day, levels = rnadays)
rec2rnaUMAP$Diet<-rec2d_rna$Diet
colnames(rec2rnaUMAP)[1:2]<-c('UMAP1','UMAP2')
rec2rnaUMAPplot<-ggplot(rec2rnaUMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for Record-seq data")

rna2sds <- rowSds(as.matrix(rna2_tf))
o <- order(rna2sds, decreasing = TRUE)
rna2PCA<-prcomp(t(rna2_tf[o[1:500],]))
pca_stat<-summary(rna2PCA)
rna2.pca_variance<-pca_stat$importance[2,]
rna2PCA<-as.data.frame(rna2PCA$x)
rna2PCA$Diet<-rna2d$Diet
rna2PCA$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP<-umap(rna2PCA[,1:(ncol(rna2PCA)-2)], custom.config)
rna2UMAP<-as.data.frame(rna2UMAP$layout)
rna2UMAP$Day<-factor(rna2d$Day, levels = rnadays)
rna2UMAP$Diet<-rna2d$Diet
colnames(rna2UMAP)[1:2]<-c('UMAP1','UMAP2')
rna2UMAPplot<-ggplot(rna2UMAP, aes(x=UMAP1, y=UMAP2, fill=Diet,  size=Day))+geom_point(pch=21, colour='#000000', stroke=0.25)+theme_pub+scale_size_discrete(range=c(1,2.5))+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+ guides(fill = guide_legend(override.aes = list(size=2)))+scale_fill_manual(values = as.vector(colour_code$Diet))+ggtitle("UMAP plot for RNA-seq data")
rec2rnaUMAPplot+rna2UMAPplot+plot_annotation(tag_levels = 'A')

4.3 Discovery of differentially expressed genes (DEGs):

We identify DEGs for day 7 (since this is the final day when the mice are fed separate diets, and RNA-seq is also available for this day). We define DEGs as genes identified to be significantly differentially expressed using a threshold (padj < 0.05) by both DESeq2 and edgeR (multiple testing, since we have 3 groups).

rec2.deseq<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='DESeq2')
rec2.edger<-recoRdseq.DE(rec2_day7, rec2d_day7, tool='edgeR')
rec2.deseq.genes<-recoRdseq.filterDEG(rec2.deseq, p = 0.05)
rec2.edger.genes<-recoRdseq.filterDEG(rec2.edger, p = 0.05)
rec2.DEG<-rec2.deseq[intersect(rec2.deseq.genes, rec2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.deseq))))]
rec2.DEG$geneID<-as.character(rec2.DEG$geneID)
rec2.DEG<-rec2.DEG[order(rec2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rec2.DEG)), grep("rrl", rownames(rec2.DEG)))
if(length(ribosomal)>0){
  rec2.DEG<-rec2.DEG[-ribosomal,]
}
rna2.deseq<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='DESeq2')
rna2.edger<-recoRdseq.DE(rna2_day7, rna2d_day7, tool='edgeR')
rna2.deseq.genes<-recoRdseq.filterDEG(rna2.deseq, p = 0.05)
rna2.edger.genes<-recoRdseq.filterDEG(rna2.edger, p = 0.05)
rna2.DEG<-rna2.deseq[intersect(rna2.deseq.genes, rna2.edger.genes), c(1,7, which(grepl('log2FoldChange', colnames(rna2.deseq))))]
rna2.DEG$geneID<-as.character(rna2.DEG$geneID)
rna2.DEG<-rna2.DEG[order(rna2.DEG$padj),]
ribosomal<-c(grep("rrs", rownames(rna2.DEG)), grep("rrl", rownames(rna2.DEG)))
if(length(ribosomal)>0){
  rna2.DEG<-rna2.DEG[-ribosomal,]
}
rec2.novel<-rec2.DEG[-which(rec2.DEG$geneID%in%rna2.DEG$geneID),]

We also look for DE genes over days 2-7 in Record-seq using a looser confidence threshold (padj <0.1) to identify consistent diet-signature genes.

rec2.DEG.list<-list()
rec2.er<-list()
rec2.de<-list()
rec2.global.DEG<-c()
for(i in unique(rec2d$Day)){
  if(i<8){
    dt<-rec2d[which(rec2d$Day==i), 1, drop=FALSE]
    dt$Diet<-factor(dt$Diet)
    de<-rec2[,which(colnames(rec2)%in%rownames(dt))]
    rec2.de[[i]]<-recoRdseq.DE(de,dt,tool='DESeq2')
    rec2.er[[i]]<-recoRdseq.DE(de,dt,tool='edgeR')
    rec2.de.genes<-recoRdseq.filterDEG(rec2.de[[i]], p = 0.1)
    rec2.er.genes<-recoRdseq.filterDEG(rec2.er[[i]], p = 0.1)
    rec2.DEG.list[[i]]<-rec2.de[[i]][intersect(rec2.de.genes, rec2.er.genes), c(1,7, which(grepl('log2FoldChange', colnames(rec2.de[[i]]))))]
    rec2.global.DEG<- c(rec2.global.DEG, as.character(intersect(rec2.de.genes,rec2.er.genes)))
  }
}
rec2.global.DEG1<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
rec2.global.DEG2<-as.data.frame(table(rec2.global.DEG)[order(table(rec2.global.DEG), decreasing = TRUE)])
colnames(rec2.global.DEG1)<-c("geneID", "days_DE")
colnames(rec2.global.DEG2)<-c("geneID", "days_DE")
rec2.global.DEG1$geneID<-as.character(rec2.global.DEG1$geneID)
rec2.global.DEG2$geneID<-as.character(rec2.global.DEG2$geneID)
for(i in unique(rec2d$Day)){
  if(i<8){
  rec2.global.DEG1$V1<-rec2.de[[i]][rec2.global.DEG1$geneID,4]
  colnames(rec2.global.DEG1)[ncol(rec2.global.DEG1)]<-paste0("log2FoldChange_FC_day", i)
  rec2.global.DEG2$V1<-rec2.de[[i]][rec2.global.DEG2$geneID,9]
  colnames(rec2.global.DEG2)[ncol(rec2.global.DEG2)]<-paste0("log2FoldChange_SC_day", i)

  }
}
rec2.global.DEG1$log2FoldChange.max<-rec2.global.DEG1[,3:ncol(rec2.global.DEG1)][cbind(1:nrow(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), max.col(replace(x <- abs(rec2.global.DEG1[,3:ncol(rec2.global.DEG1)]), is.na(x), -Inf)))]
rec2.global.DEG2$log2FoldChange.max<-rec2.global.DEG2[,3:ncol(rec2.global.DEG2)][cbind(1:nrow(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), max.col(replace(x <- abs(rec2.global.DEG2[,3:ncol(rec2.global.DEG2)]), is.na(x), -Inf)))]
Error in `[.data.frame`(rec2.global.DEG2, , 3:ncol(rec2.global.DEG2)) : 
  undefined columns selected

4.4 Plotting individual DEGs:

We plot vst-transformed genome-mapping spacer counts for 5 genes in the gntR pathway for chow and starch fed mice on day 7.

gntR_genes<-c('eda','edd', 'gntT', 'kdgT', 'gntU', 'gntK')
de<-rec2d_day7[rec2d_day7$Diet!='Fat',]
dt<-rec2_day7tf[gntR_genes, rownames(de)]
rec2.gntR.plot.df<-data.frame(Diet=de$Diet, t(dt))
rec2.gntR.plot.df<-melt(rec2.gntR.plot.df, id.vars = 'Diet')
rec2.gntR.plot<-ggplot(rec2.gntR.plot.df, aes(y=value, x=variable, fill=Diet, color='black'))+geom_boxplot(size=0.24, outlier.size=0)+geom_point(size=0.48, position = position_dodge(0.75))+theme_pub+ylab("gene-mapping spacer counts (vst-transformed)")+xlab("gene")+ggtitle("Record-seq counts on day 7 for gntR gene")+scale_fill_manual(values = as.vector(colour_code$Diet[c(1,3)]))+scale_color_manual(values = c("black"), guide='none')
rec2.gntR.plot+plot_annotation()

4.5 Heatmap for Record-seq and RNA-seq DEGs on day 7

We plot heatmaps showing hierarchical clustering of samples using detected DE genes for both Record-seq and RNA-seq on day 7.

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rec2_day7tf[rec2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rec2.day7<-pheatmap(dheatmap, annotation_col = rec2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0,  treeheight_col = 5, show_colnames = FALSE, show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='Record-seq DEGs day 7')

heatmap.genelist<-heatmap.rec2.day7$tree_row$labels[heatmap.rec2.day7$tree_row$order]
heatmap.genelist<-rec2_day7tf[heatmap.genelist,]
colnames(heatmap.genelist)<-as.character(rec2d_day7$Diet)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day7tf[rna2.DEG$geneID,], 1, zscorestandardize)))
heatmap.rna2.day7<-pheatmap(dheatmap, annotation_col = rna2d_day7[,1, drop=FALSE], annotation_colors=colour_code, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = TRUE, treeheight_row = 0, treeheight_col = 5, show_colnames = FALSE,show_rownames = FALSE, color = cols,fontsize_number=5, width=2.28, height=2.28, main='RNA-seq DEGs day 7')

4.6 Volcano plots and heatmaps for Record-seq and RNA-seq DEGs:

We perform pairwise DE analysis using DESeq2 to identify log2FC and p-adj values for each diet pair on day 7, and plot volcanoes (log2FC>1.5, padj<0.1)

Record-seq:

levels<-sort(unique(rec2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rec2.de.vals<-list()
rec2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rec2d_day7[which(rec2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rec2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rec2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rec2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rec2.de.vals[[i]]) <- rec2.de.vals[[i]]$geneID
  rownames(rec2.ed.vals[[i]]) <- rec2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rec2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rec2.de.vals[[i]]<-rec2.de.vals[[i]][complete.cases(rec2.de.vals[[i]]),]
  rec2.de.vals[[i]]$Group<-'None'
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange>1.5&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rec2.de.vals[[i]]$Group[ which(rec2.de.vals[[i]]$log2FoldChange<(-1.5)&rec2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rec2.de.vals[[i]]$Group<-factor(rec2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rec2.de.vals[[i]]$label<-FALSE
  m1<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rec2.de.vals[[i]][rec2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rec2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rec2.de.vals[[i]]$log2FoldChange[j])>1.5&rec2.de.vals[[i]]$padj[j]<0.1){
      rec2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rec2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rec2.de.vals[[i]][which(rec2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

RNA-seq:

levels<-sort(unique(rna2d_day7[,1]))
pairwise.combo<-combn(levels, 2)
color.combo<-combn(colour_code$Diet, 2)
rna2.de.vals<-list()
rna2.ed.vals<-list()
vol.plots<-list()
DEG<-list()
for(i in 1:dim(pairwise.combo)[2]){
  ds<-rna2d_day7[which(rna2d_day7[,1]%in%pairwise.combo[,i]),]
  ds$Diet<-as.character(ds$Diet)
  dt<-rna2_day7[,rownames(ds)]
  dtf<-recoRdseq.transform(dt,ds)
  rna2.de.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'DESeq2')
  rna2.ed.vals[[i]] <- recoRdseq.DE(dt, ds, tool = 'edgeR')
  rownames(rna2.de.vals[[i]]) <- rna2.de.vals[[i]]$geneID
  rownames(rna2.ed.vals[[i]]) <- rna2.ed.vals[[i]]$geneID
  deseq.genes<-recoRdseq.filterDEG(rna2.de.vals[[i]], p = 0.1)
  edger.genes<-recoRdseq.filterDEG(rna2.ed.vals[[i]], p = 0.1)
  DEG[[i]]<-data.frame(row.names=intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes),log2Foldchange=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 4], padj=rna2.de.vals[[i]][intersect(deseq.genes, edger.genes), 7])
  ribosomal<-c(grep("rrs", rownames(DEG[[i]])), grep("rrl", rownames(DEG[[i]])))
  if(length(ribosomal)>0){
      DEG[[i]]<-DEG[[i]][-ribosomal,]
  }
  DEG[[i]]$geneID<-as.character(DEG[[i]]$geneID)
  rna2.de.vals[[i]]<-rna2.de.vals[[i]][complete.cases(rna2.de.vals[[i]]),]
  rna2.de.vals[[i]]$Group<-'None'
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange>1.5&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[2]))
  rna2.de.vals[[i]]$Group[ which(rna2.de.vals[[i]]$log2FoldChange<(-1.5)&rna2.de.vals[[i]]$padj<0.1)]<-paste0("upregulated.in.", sort(unique(ds$Diet))[1])
 rna2.de.vals[[i]]$Group<-factor(rna2.de.vals[[i]]$Group, levels = c(paste0("upregulated.in.", as.character(sort(unique(ds$Diet))[1])), paste0("upregulated.in.", sort(unique(ds$Diet))[2]), 'None' ))
  rna2.de.vals[[i]]$label<-FALSE
  m1<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange>1.5, 'geneID'][1:10]
  m2<-rna2.de.vals[[i]][rna2.de.vals[[i]]$log2FoldChange<(-1.5), 'geneID'][1:10]
  m<-which(rna2.de.vals[[i]]$geneID%in%union(m1,m2))
  for(j in m){
    if(abs(rna2.de.vals[[i]]$log2FoldChange[j])>1.5&rna2.de.vals[[i]]$padj[j]<0.1){
      rna2.de.vals[[i]]$label[j]<-TRUE
    }
  }
  vol.plots[[i]]<-ggplot(rna2.de.vals[[i]], aes( x=log2FoldChange, y=(-log10(padj)), color=Group))+scale_colour_manual(values = c(color.combo[,i], 'gray70'))+geom_point(size=0.24)+geom_text_repel(data = rna2.de.vals[[i]][which(rna2.de.vals[[i]]$label),], aes( x=log2FoldChange, y=(-log10(padj)), label=geneID), size=1.76, show.legend=FALSE)+theme_pub+geom_vline(xintercept = 1.5, size=0.24)+geom_vline(xintercept = -1.5, size=0.24)+geom_hline(yintercept = 1, size=0.24)+xlab("log2 fold change")+ylab("-log10 p-adjusted value")+guides(color = guide_legend(override.aes = list(size=1.5)))
}

vol.plots[[1]] + vol.plots[[2]] +vol.plots[[3]] + plot_annotation(tag_levels = 'A')+plot_layout(ncol = 2)

NA
NA
NA

4.7 Hierarchical clustering on final day using DEGs

We want to check whether information about diet groups prior to switch can be retrieved at day 20 - i.e 13 days after the switch. For this, we use diet signature genes identified before the switch (DEGs) to hierarchically cluster the groups. We can almost perfectly classify groups using Record-seq data, while for RNA-seq, the groups converge.

rec2d_day20<-rec2d[rec2d$Day==20,]
rec2_day20<-rec2[,rownames(rec2d_day20)]
rec2_day20_tf<-recoRdseq.transform(rec2_day20, rec2d_day20)

cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
ribosomal<-c(grep("rrs", rec2.global.DEG$geneID), grep("rrl", rec2.global.DEG$geneID))
if(length(ribosomal)>0){
  rec2.global.DEG<-rec2.global.DEG[-ribosomal,]
}
rec2.geneShortList<-unique(c(rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_FC>2.5), 1][1:10], rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC>2.5), 1][1:10],rec2.global.DEG[which(rec2.global.DEG$log2FoldChange.max_SC<(-2.5)&rec2.global.DEG$log2FoldChange.max_FC<(-2.5)), 1][1:10]))
dheatmap<-as.data.frame(t(apply(rec2_day20_tf[rec2.geneShortList,], 1, zscorestandardize)))
heatmap.rec2<-pheatmap(dheatmap, annotation_col = rec2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE,color = cols,fontsize_number=5, main='Hierarchical clustering of Record-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rec2_day20_tf[rec2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rec2d_day20$Diet)

rna2d_day20<-rna2d[rna2d$Day==20,]
rna2_day20<-rna2[,rownames(rna2d_day20)]
rna2_day20_tf<-recoRdseq.transform(rna2_day20, rna2d_day20)
rna2.geneShortList<-unique(c(rna2.DEG[rna2.DEG$log2FoldChange.Fat_vs_Chow>2.5, 1][1:10], rna2.DEG[rna2.DEG$log2FoldChange.Starch_vs_Chow>2.5, 1][1:10], rna2.DEG[which(rowMeans(rna2.DEG[,3:4])<(-2.5)), 1][1:10]))
cols<- colorRampPalette(c("dodgerblue4", "white","violetred4"))(256)
dheatmap<-as.data.frame(t(apply(rna2_day20_tf[rna2.geneShortList,], 1, zscorestandardize)))
heatmap.rna2<-pheatmap(dheatmap, annotation_col = rna2d_day20[,1, drop=FALSE], annotation_colors=colour_code, treeheight_row=0, fontsize = 5, fontsize_row = 5, fontsize_col = 5, cluster_rows = FALSE, show_colnames = FALSE, color = cols,fontsize_number=5, main='Hierarchical clustering of RNA-seq data on day 20 based on diet signature genes')

heatmap.genelist<-rna2_day20_tf[rna2.geneShortList,]
colnames(heatmap.genelist)<-as.character(rna2d_day20$Diet)

4.8 Ecocyc analysis:

We create enrichment plots for top differentially regulated pathways identified by Ecocyc using the pairwise DEG lists generated here.

ChowXStarch.pathway<-as.data.frame(read.table("data/Chow.Starch.pathway.txt", header=TRUE, sep = '\t'))
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Chow'])*(-1)
ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch']<-log10(ChowXStarch.pathway$p.values[ChowXStarch.pathway$group=='enriched.in.Starch'])
ChowXStarch.pathway<-ChowXStarch.pathway[order(ChowXStarch.pathway$p.values),]
ChowXStarch.pathway.plot<-ggplot(ChowXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,3)])))

ChowXStarch.pathway.plot+plot_annotation()

ChowXFat.pathway<-as.data.frame(read.table("data/Chow.Fat.pathway.txt", header=TRUE, sep = '\t'))
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Chow'])*(-1)
ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat']<-log10(ChowXFat.pathway$p.values[ChowXFat.pathway$group=='enriched.in.Fat'])
ChowXFat.pathway<-ChowXFat.pathway[order(ChowXFat.pathway$p.values),]
ChowXFat.pathway.plot<-ggplot(ChowXFat.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=ChowXFat.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(1,2)])))

ChowXFat.pathway.plot+plot_annotation()

FatXStarch.pathway<-as.data.frame(read.table("data/Fat.Starch.pathway.txt", header=TRUE, sep = '\t'))
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Fat'])*(-1)
FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch']<-log10(FatXStarch.pathway$p.values[FatXStarch.pathway$group=='enriched.in.Starch'])
FatXStarch.pathway<-FatXStarch.pathway[order(FatXStarch.pathway$p.values),]
FatXStarch.pathway.plot<-ggplot(FatXStarch.pathway, aes(x=Pathway, y=p.values, size=number.of.genes, colour=group ))+geom_point()+coord_flip()+theme_pub+scale_size_continuous(range = c(1,3))+geom_hline(yintercept = (-1.30103), size=0.24)+geom_hline(yintercept = 0, size=0.24)+geom_hline(yintercept = 1.30103, size=0.24)+xlab("")+ylab("-log10 p-adjusted value")+ labs(size = "Genes detected")+scale_x_discrete(limits=FatXStarch.pathway$Pathway)+scale_color_manual(values = c(as.character(colour_code$Diet[c(2,3)])))

FatXStarch.pathway.plot+plot_annotation()

5 Checking reproducibility of classifier genes (DEGs)

We want to confirm that there is an overlap among the DEGs identified in the two experimental replicates, and the direction of differential regulation is consistent. We use the genes that are upregulated/downregulated in the Chow group compared to the Starch group on day 7.

deseq.genes<-recoRdseq.filterDEG(rec1.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec1.ed.vals[[2]], p = 0.1)
rec1.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec1.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

deseq.genes<-recoRdseq.filterDEG(rec2.de.vals[[2]], p = 0.1)
edger.genes<-recoRdseq.filterDEG(rec2.ed.vals[[2]], p = 0.1)
rec2.Chow.v.Starch.DEG<-data.frame(row.names = intersect(deseq.genes, edger.genes),geneID=intersect(deseq.genes, edger.genes), log2FC=rec2.de.vals[[2]][intersect(deseq.genes, edger.genes),4])

plot(euler(list(rec1 = rec1.Chow.v.Starch.DEG$geneID, rec2 = rec2.Chow.v.Starch.DEG$geneID)) , quantities=TRUE)

Finally, we plot a correlation plot based on the log2FC detected for DEGs for the two experiments, and estimate the number of DEGs regulated in a similar direction.

 DEG.compare<-data.frame(geneID=intersect(rec1.Chow.v.Starch.DEG$geneID, rec2.Chow.v.Starch.DEG$geneID))
DEG.compare$geneID<-as.character(DEG.compare$geneID)
DEG.compare$rec1_log2FC<-rec1.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare$rec2_log2FC<-rec2.Chow.v.Starch.DEG[DEG.compare$geneID,2]
DEG.compare<-DEG.compare[complete.cases(DEG.compare), ]
r2<-round(cor(DEG.compare$rec1_log2FC, DEG.compare$rec2_log2FC)^2,2)
n<-round(length(which(DEG.compare$rec1_log2FC*DEG.compare$rec2_log2FC>0))*100/length(DEG.compare$geneID))
DEG.scatterplot<-ggplot(DEG.compare, aes(y=rec1_log2FC, x=rec2_log2FC))+geom_point(size=0.48, aes(colour='gray10'))+geom_smooth(method = 'lm', se = FALSE, size=0.48)+theme_pub+geom_hline(yintercept = 0, size=0.24)+geom_vline(xintercept = 0, size=0.24)+xlab("log2 fold change of DEGs detected in transient diet 2")+ylab("corresponding log2 fold change in transient diet 1")+annotate("text",  x=Inf, y = Inf, label = paste0("R^2 =",as.character(r2), " \n DEGs regulated in the same direction = ",as.character(n), "%"), vjust=1, hjust=1, size=3)+scale_color_manual(values = c('gray10'), guide='none')+ggtitle("Correlation of overlapping DEGs detected in both experiments")
DEG.scatterplot+plot_annotation()

6 Information about R session

sessionInfo()
LS0tCnRpdGxlOiAiVHJhbnNpZW50IERpZXRzIEkgKyBJSSBhbmFseXNpcyAtIFJOQS1zZXEgJiBSZWNvcmQtc2VxIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICcyJwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICAKICBnaXRodWJfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCiAgICAKICBodG1sX25vdGVib29rOgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IGRhcmtseQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrCiAgICAKLS0tCiMgSW50cm9kdWN0aW9uIAoKVGhpcyBSIG5vdGVib29rIGltcGxlbWVudHMgdGhlIGFuYWx5c2lzIG9mIHRoZSBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIHJlYWRvdXQgb2YgdGhlIHRyYW5zaWVudCBkaWV0IGV4cGVyaW1lbnRzIGRlc2NyaWJlZCBpbiAnVHJhbnNjcmlwdGlvbmFsIFJlY29yZGluZ3MgaW4gdGhlIGd1dCcgbWFudXNjcmlwdC4gVGhlIGZvbGxvd2luZyBmaWxlcyBuZWVkIHRvIGJlIHN0b3JlZCBpbiB0aGUgZGlyZWN0b3J5IGBkYXRhYCB3aXRoaW4gdGhlIHdvcmtpbmcgZGlyZWN0b3J5OiAKCiAgICB0cmFuc2llbnQtZGlldC8KICAgICAgICBzZWNvbmRhcnlBbmFseXNpcy5SbWQKICAgICAgICBkYXRhLwogICAgICAgICAgICB0cmFuc2llbnREaWV0MV9SZWNvcmRzZXFfZ2Vub21lbWFwcGluZy50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfUmVjb3Jkc2VxX2Rlc2lnbm1hdHJpeC50eHQKICAgICAgICAgICAgdHJhbnNpZW50RGlldDFfUk5Bc2VxX2dlbm9tZW1hcHBpbmcudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQxX1JOQXNlcV9kZXNpZ25tYXRyaXgudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQyX1JlY29yZHNlcV9nZW5vbWVtYXBwaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0Ml9SZWNvcmRzZXFfcGxhc21pZG1hcHBpbmcudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQyX1JlY29yZHNlcV9kZXNpZ25tYXRyaXgudHh0CiAgICAgICAgICAgIHRyYW5zaWVudERpZXQyX1JOQXNlcV9nZW5vbWVtYXBwaW5nLnR4dAogICAgICAgICAgICB0cmFuc2llbnREaWV0Ml9STkFzZXFfZGVzaWdubWF0cml4LnR4dAogICAgICAgICAgICBDaG93LkZhdC5wYXRod2F5LnR4dAogICAgICAgICAgICBDaG93LlN0YXJjaC5wYXRod2F5LnR4dAogICAgICAgICAgICBGYXQuU3RhcmNoLnBhdGh3YXkudHh0CiAgICAgICAgCiMgTGlicmFyaWVzCgpUaGUgYHJlY29SZHNlcWAgcGFja2FnZSBhbmQgZGVwZW5kZW5jaWVzIGFyZSByZXF1aXJlZCBmb3IgdGhpcyBhbmFseXNpcywgYW5kIHRoZSBmYW50YXN0aWMgYHBhdGNod29ya2AgcGFja2FnZSBpcyB1c2VkIGZvciB2aXN1YWxpemF0aW9uLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmlmKCFyZXF1aXJlKGRldnRvb2xzKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQp9CmxpYnJhcnkoZGV2dG9vbHMpCmlmKCFyZXF1aXJlKGV1bGVycikpewogIGluc3RhbGwucGFja2FnZXMoImV1bGVyciIpCn0KbGlicmFyeShldWxlcnIpCmlmKCFyZXF1aXJlKHBseXIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJwbHlyIikKfQpsaWJyYXJ5KHBseXIpCmlmKCFyZXF1aXJlKHJlY29SZHNlcSkpewogIGluc3RhbGxfZ2l0aHViKCJwbGF0dGxhYi9UcmFuc2NyaXB0aW9uYWwtUmVjb3JkaW5nIiwgc3ViZGlyPSJyZWNvUmRzZXEiKQp9CmlmKCFyZXF1aXJlKGZhY3RvZXh0cmEpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikKfQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmlmKCFyZXF1aXJlKHBhdGNod29yaykpewogIGluc3RhbGwucGFja2FnZXMoJ3BhdGNod29yaycpCn0KaWYoIXJlcXVpcmUoZ2dyZXBlbCkpewogIGluc3RhbGwucGFja2FnZXMoJ2dncmVwZWwnKQp9CmxpYnJhcnkocmVjb1Jkc2VxKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGdncmVwZWwpCmNvbG91cl9jb2RlID0gbGlzdCgKICBEaWV0ID0gYyhDaG93ID0gIiM1MzhiY2UiLCBGYXQ9IiNlZDkxNWMiLCBTdGFyY2g9JyM0MmJiN2YnKSkgIyB3ZSBzZXQgYSBjb25zaXN0ZW50IGNvbG9yIHNjaGVtZSBmb3IgdGhlIHRocmVlIGRpZXQgZ3JvdXBzCgp0aGVtZV9wdWI8LXRoZW1lX21pbmltYWwoKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsIGxlZ2VuZC5qdXN0aWZpY2F0aW9uPSJjZW50ZXIiLCBsZWdlbmQubWFyZ2luPW1hcmdpbigwLDAsMCwwKSxsZWdlbmQuYm94Lm1hcmdpbj1tYXJnaW4oLTEwLC0xMCwtMTAsLTEwKSxwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgbGVnZW5kLnNwYWNpbmcueSA9ICB1bml0KDAsICdtbScpLCBsZWdlbmQuYm94PSd2ZXJ0aWNhbCcsIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC4xLCAiY20iKSxsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjEsImNtIiksIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTUpLCB0ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9NSksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTUsIGNvbG91cj0nYmxhY2snKSwgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gMC4yNCwgY29sb3VyPSdncmF5MScsIGxpbmV0eXBlID0gMikpICMgd2Ugc2V0IGEgY29uc2lzdGVudCB0aGVtZSBmb3IgZ2dwbG90IG9iamVjdHMKCmN1c3RvbS5jb25maWcgPSB1bWFwLmRlZmF1bHRzCmN1c3RvbS5jb25maWckcmFuZG9tX3N0YXRlID0gMgpgYGAKIyBUcmFuc2llbnQgRGlldCAxCgpEYXRhIGZvciB0aGUgdHJhbnNpZW50IGRpZXQgZXhwZXJpbWVudCB3aXRoIDE0IGRheXMgaXMgYW5hbHl6ZWQgZmlyc3QuCgojIyBJbXBvcnRpbmcgYW5kIHByZS1wcm9jZXNzaW5nIGRhdGEgZm9yIHRyYW5zaWVudCBkaWV0IDEKCiAgV2UgaW1wb3J0IHRoZSBkYXRhIG1hdHJpY2VzIGZvciBib3RoIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEsIGZpbHRlciB0aGVtIGZvciBsb3dseSBleHByZXNzZWQgZ2VuZXMgYXMgd2VsbCBhcyBvdXRsaWVyIHNhbXBsZXMgd2l0aCBsb3cgY3VtdWxhdGl2ZSBjb3VudHMsIGFuZCB1c2UgdnN0IGZyb20gX0RFU2VxMl8gdG8gbm9ybWFsaXplIGFuZCB0cmFuc2Zvcm0gdGhlIGRhdGEuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVjMTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX1JlY29yZHNlcV9nZW5vbWVhbGlnbmluZy50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcmVjMWQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9SZWNvcmRzZXFfZGVzaWdubWF0cml4LnR4dCIsIGhlYWRlciA9IFRSVUUpKQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJlYzEsIHJlYzFkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMCkKcmVjMTwtREVMaXN0W1sxXV0KcmVjMWQ8LURFTGlzdFtbMl1dCnJlYzFkPC1yZWMxZFtyZWMxZCREYXk+MSxdCnJlYzE8LXJlYzFbLHJvd25hbWVzKHJlYzFkKV0KcmVjMV90ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMxLCByZWMxZCx0cmFuc2Zvcm1hdGlvbiA9ICd2c3QnKQoKcm5hMTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX1JOQXNlcV9nZW5vbWVhbGlnbmluZy50eHQiLCBoZWFkZXIgPSBUUlVFKSkKcm5hMWQ8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS90cmFuc2llbnREaWV0MV9STkFzZXFfZGVzaWdubWF0cml4LnR4dCIsIGhlYWRlciA9IFRSVUUpKQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJuYTEsIHJuYTFkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMDApCnJuYTE8LURFTGlzdFtbMV1dCnJuYTFkPC1ERUxpc3RbWzJdXQpybmExX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTEsIHJuYTFkKQpybmFkYXlzPC11bmlxdWUocm5hMWQkRGF5KQpybmExX2RheTE0PC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDFfZGF5MTRfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpybmExZF9kYXkxNDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQxX2RheTE0X1JOQXNlcV9kZXNpZ25tYXRyaXgudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJuYTFkX2RheTE0PC1ybmExZF9kYXkxNFssMTozXQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJuYTFfZGF5MTQsIHJuYTFkX2RheTE0LCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMDApCnJuYTFfZGF5MTQ8LURFTGlzdFtbMV1dCnJuYTFkX2RheTE0PC1ERUxpc3RbWzJdXQpybmExX2RheTE0dGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocm5hMV9kYXkxNCwgcm5hMWRfZGF5MTQsIHRyYW5zZm9ybWF0aW9uID0gJ3ZzdCcpCgpgYGAKCiMjIERhdGEgZXhwbG9yYXRpb24KICBXZSB1c2UgUHJpbmNpcGFsIENvbXBvbmVudCBhbmFseXNpcyBhbmQgVU1BUCBmb3IgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIGFuZCBleHBsb3JpbmcgY2x1c3RlcnMgaW4gYW4gdW5zdXBlcnZpc2VkIGZhc2hpb24gaW4gb3VyIGRhdGEuICBXZSBmaXJzdCBnZW5lcmF0ZSB0aGVzZSBmb3IgdGhlIGVudGlyZSBkYXRhc2V0IGZyb20gUmVjb3JkLXNlcToKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsICBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTN9CnJlYzFzZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMxX3RmKSkKbyA8LSBvcmRlcihyZWMxc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMVBDQTwtcHJjb21wKHQocmVjMV90ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMVBDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMxUENBPC1hcy5kYXRhLmZyYW1lKHJlYzFQQ0EkeCkKcmVjMVBDQSREaWV0PC1yZWMxZCREaWV0CnJlYzFQQ0EkRGF5PC1mYWN0b3IocmVjMWQkRGF5LCBsZXZlbHMgPSAxOjIwKQpyZWMxUENBcGxvdDwtZ2dwbG90KHJlYzFQQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDEsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJlY29yZC1zZXEgZGF0YSAtIGFsbCBkYXlzICh0cmFuc2llbnQgZGlldCAxKSIpCgoKcmVjMVVNQVA8LXVtYXAocmVjMVBDQVssMToobmNvbChyZWMxUENBKS0yKV0sIGN1c3RvbS5jb25maWcpCnJlYzFVTUFQPC1hcy5kYXRhLmZyYW1lKHJlYzFVTUFQJGxheW91dCkKcmVjMVVNQVAkRGF5PC1mYWN0b3IocmVjMWQkRGF5LCBsZXZlbHMgPSAyOjE0KQpyZWMxVU1BUCREaWV0PC1yZWMxZCREaWV0CmNvbG5hbWVzKHJlYzFVTUFQKVsxOjJdPC1jKCdVTUFQMScsJ1VNQVAyJykKcmVjMVVNQVBwbG90PC1nZ3Bsb3QocmVjMVVNQVAsIGFlcyh4PVVNQVAxLCB5PVVNQVAyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNCkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZ3RpdGxlKCJVTUFQIHBsb3QgZm9yIFJlY29yZC1zZXEgZGF0YSAtIGFsbCBkYXlzICh0cmFuc2llbnQgZGlldCAxKSIpCgpyZWMxUENBcGxvdCtyZWMxVU1BUHBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCiAgRm9yIGEgY29tcGFyaXNvbiBiZXR3ZWVuIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEsIHdlIGNoZWNrIGlmIHRoZSBkaWV0IGdyb3VwcyBjYW4gYmUgY2xhc3NpZmllZCB1c2luZyBQQ0Egb24gZGF5IDcgKHRoZSBsYXN0IGRheSB3aGVuIHRoZSBtaWNlIGFyZSBmZWQgZGlmZmVyZW50IGRpZXRzLCBiZWZvcmUgc3dpdGNoaW5nIGFsbCBtaWNlIHRvIGEgJ0Nob3cnIGRpZXQpCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTN9CgpyZWMxZF9kYXk3PC1yZWMxZFtyZWMxZCREYXk9PTcsXQpyZWMxX2RheTc8LXJlYzFbLCByb3duYW1lcyhyZWMxZF9kYXk3KV0KcmVjMV9kYXk3dGY8LXJlY29SZHNlcS50cmFuc2Zvcm0ocmVjMV9kYXk3LCByZWMxZF9kYXk3KQpyZWMxX2RheTdfc2RzIDwtIHJvd1Nkcyhhcy5tYXRyaXgocmVjMV9kYXk3dGYpKQpvIDwtIG9yZGVyKHJlYzFfZGF5N19zZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMxX2RheTdQQ0E8LXByY29tcCh0KHJlYzFfZGF5N3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMxX2RheTdQQ0EpCnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMV9kYXk3UENBPC1hcy5kYXRhLmZyYW1lKHJlYzFfZGF5N1BDQSR4KQpyZWMxX2RheTdQQ0EkRGlldDwtcmVjMWRfZGF5NyREaWV0CnJlYzFfZGF5N1BDQSREYXk8LWZhY3RvcihyZWMxZF9kYXk3JERheSwgbGV2ZWxzID0gYyg3KSkKcmVjMV9kYXk3UENBcGxvdDwtZ2dwbG90KHJlYzFfZGF5N1BDQSwgYWVzKHg9UEMxLCB5PVBDMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMiwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkreWxhYihwYXN0ZTAoIlBDMiAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUmVjb3JkLXNlcSBkYXRhIG9uIGRheSA3ICh0cmFuc2llbnQgZGlldCAxKSIpCgpybmExX3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJuYTFfdGYpKQpvIDwtIG9yZGVyKHJuYTFfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcm5hMV9QQ0E8LXByY29tcCh0KHJuYTFfdGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJuYTFfUENBKQpwY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJuYTFfUENBPC1hcy5kYXRhLmZyYW1lKHJuYTFfUENBJHgpCnJuYTFfUENBJERpZXQ8LXJuYTFkJERpZXQKcm5hMV9QQ0EkRGF5PC1mYWN0b3Iocm5hMWQkRGF5LCBsZXZlbHMgPSBjKDcpKQpybmExX2RheTdQQ0FwbG90PC1nZ3Bsb3Qocm5hMV9QQ0EsIGFlcyh4PVBDMSwgeT1QQzIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDIsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3lsYWIocGFzdGUwKCJQQzIgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzFdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkrZ2d0aXRsZSgiIFBDQSBwbG90IG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgNyAodHJhbnNpZW50IGRpZXQgMSkgIikKCgpybmExX2RheTdQQ0FwbG90K3JlYzFfZGF5N1BDQXBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCiAgTmV4dCwgd2UgY2hlY2sgaWYgUmVjb3JkLXNlcSBvciBSTkEtc2VxIGNhbiBkaXN0aW5ndWlzaCB0aGUgc2FtcGxlcyBvbiBkYXkgMTQgKDcgZGF5cyBhZnRlciBzd2l0Y2hpbmcgYWxsIHNhbXBsZXMgdG8gQ2hvdyBkaWV0KS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KCnJlYzFkX2RheTE0PC1yZWMxZFtyZWMxZCREYXk9PTE0LF0KcmVjMV9kYXkxNDwtcmVjMVssIHJvd25hbWVzKHJlYzFkX2RheTE0KV0KcmVjMV9kYXkxNHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzFfZGF5MTQsIHJlYzFkX2RheTE0KQpyZWMxX2RheTE0X3NkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzFfZGF5MTR0ZikpCm8gPC0gb3JkZXIocmVjMV9kYXkxNF9zZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMxX2RheTE0UENBPC1wcmNvbXAodChyZWMxX2RheTE0dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzFfZGF5MTRQQ0EpCnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMV9kYXkxNFBDQTwtYXMuZGF0YS5mcmFtZShyZWMxX2RheTE0UENBJHgpCnJlYzFfZGF5MTRQQ0EkRGlldDwtcmVjMWRfZGF5MTQkRGlldApyZWMxX2RheTE0UENBJERheTwtZmFjdG9yKHJlYzFkX2RheTE0JERheSwgbGV2ZWxzID0gYygxNCkpCnJlYzFfZGF5MTRQQ0FwbG90PC1nZ3Bsb3QocmVjMV9kYXkxNFBDQSwgYWVzKHg9UEMxLCB5PVBDMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMiwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KSsgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkreWxhYihwYXN0ZTAoIlBDMiAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsyXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK3hsYWIocGFzdGUwKCJQQzEgKCIsIGFzLmNoYXJhY3RlcihwY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUmVjb3JkLXNlcSBkYXRhIG9uIGRheSAxNCAodHJhbnNpZW50IGRpZXQgMSkiKQoKcm5hMV9kYXkxNF9zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmExX2RheTE0dGYpKQpvIDwtIG9yZGVyKHJuYTFfZGF5MTRfc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcm5hMV9kYXkxNF9QQ0E8LXByY29tcCh0KHJuYTFfZGF5MTR0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocm5hMV9kYXkxNF9QQ0EpCnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0Kcm5hMV9kYXkxNF9QQ0E8LWFzLmRhdGEuZnJhbWUocm5hMV9kYXkxNF9QQ0EkeCkKcm5hMV9kYXkxNF9QQ0EkRGlldDwtcm5hMWRfZGF5MTQkRGlldApybmExX2RheTE0X1BDQSREYXk8LWZhY3RvcihybmExZF9kYXkxNCREYXksIGxldmVscyA9IGMoMTQpKQpybmExX2RheTE0UENBcGxvdDwtZ2dwbG90KHJuYTFfZGF5MTRfUENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSTkEtc2VxIGRhdGEgb24gZGF5IDE0ICh0cmFuc2llbnQgZGlldCAxKSAiKQoKCnJuYTFfZGF5MTRQQ0FwbG90K3JlYzFfZGF5MTRQQ0FwbG90K3Bsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQoKYGBgCgoKIyMgRGlzY292ZXJ5IG9mIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcyk6CiAgV2UgaWRlbnRpZnkgREVHcyBmb3IgZGF5IDcgKHNpbmNlIHRoaXMgaXMgdGhlIGZpbmFsIGRheSB3aGVuIHRoZSBtaWNlIGFyZSBmZWQgc2VwYXJhdGUgZGlldHMsIGFuZCBSTkEtc2VxIGlzIGFsc28gYXZhaWxhYmxlIGZvciB0aGlzIGRheSkuIFdlIGRlZmluZSBERUdzIGFzIGdlbmVzIGlkZW50aWZpZWQgdG8gYmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgdXNpbmcgYSB0aHJlc2hvbGQgKHBhZGogPCAwLjA1KSBieSBib3RoIERFU2VxMiBhbmQgZWRnZVIgKG11bHRpcGxlIHRlc3RpbmcsIHNpbmNlIHdlIGhhdmUgMyBncm91cHMpLiAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlYzEuZGVzZXE8LXJlY29SZHNlcS5ERShyZWMxX2RheTcsIHJlYzFkX2RheTcsIHRvb2w9J0RFU2VxMicpCnJlYzEuZWRnZXI8LXJlY29SZHNlcS5ERShyZWMxX2RheTcsIHJlYzFkX2RheTcsIHRvb2w9J2VkZ2VSJykKcmVjMS5kZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmRlc2VxLCBwID0gMC4wNSkKcmVjMS5lZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmVkZ2VyLCBwID0gMC4wNSkKcmVjMS5ERUc8LXJlYzEuZGVzZXFbaW50ZXJzZWN0KHJlYzEuZGVzZXEuZ2VuZXMsIHJlYzEuZWRnZXIuZ2VuZXMpLCBjKDEsNywgd2hpY2goZ3JlcGwoJ2xvZzJGb2xkQ2hhbmdlJywgY29sbmFtZXMocmVjMS5kZXNlcSkpKSldCnJlYzEuREVHJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzEuREVHJGdlbmVJRCkKcmVjMS5ERUc8LXJlYzEuREVHW29yZGVyKHJlYzEuREVHJHBhZGopLF0KcmVjMS5ERUckZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocmVjMS5ERUckZ2VuZUlEKQpybmExLmRlc2VxPC1yZWNvUmRzZXEuREUocm5hMSwgcm5hMWQsIHRvb2w9J0RFU2VxMicpCnJuYTEuZWRnZXI8LXJlY29SZHNlcS5ERShybmExLCBybmExZCwgdG9vbD0nZWRnZVInKQpybmExLmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTEuZGVzZXEsIHAgPSAwLjA1KQpybmExLmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTEuZWRnZXIsIHAgPSAwLjA1KQpybmExLkRFRzwtcm5hMS5kZXNlcVtpbnRlcnNlY3Qocm5hMS5kZXNlcS5nZW5lcywgcm5hMS5lZGdlci5nZW5lcyksIGMoMSw3LCB3aGljaChncmVwbCgnbG9nMkZvbGRDaGFuZ2UnLCBjb2xuYW1lcyhybmExLmRlc2VxKSkpKV0Kcm5hMS5ERUckZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocm5hMS5ERUckZ2VuZUlEKQoKcmVjMS5ub3ZlbDwtcmVjMS5ERUdbLXdoaWNoKHJlYzEuREVHJGdlbmVJRCVpbiVybmExLkRFRyRnZW5lSUQpLF0KYGBgCiAgRm9yIFJlY29yZC1zZXEsIHdlIGFsc28gbG9vayBmb3IgREUgZ2VuZXMgb3ZlciBkYXlzIDItNyBpbiBSZWNvcmQtc2VxIHVzaW5nIGEgbG9vc2VyIGNvbmZpZGVuY2UgdGhyZXNob2xkIChwYWRqIDwwLjEpIHRvIGlkZW50aWZ5IGNvbnNpc3RlbnQgZGlldC1zaWduYXR1cmUgZ2VuZXMuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMxLkRFRy5saXN0PC1saXN0KCkKcmVjMS5lcjwtbGlzdCgpCnJlYzEuZGU8LWxpc3QoKQpyZWMxLmdsb2JhbC5ERUc8LWMoKQpmb3IoaSBpbiB1bmlxdWUocmVjMWQkRGF5KSl7CiAgaWYoaTw4KXsKICAgIGR0PC1yZWMxZFt3aGljaChyZWMxZCREYXk9PWkpLCAxLCBkcm9wPUZBTFNFXQogICAgZGU8LXJlYzFbLHdoaWNoKGNvbG5hbWVzKHJlYzEpJWluJXJvd25hbWVzKGR0KSldCiAgICByZWMxLmRlW1tpXV08LXJlY29SZHNlcS5ERShkZSxkdCx0b29sPSdERVNlcTInKQogICAgcmVjMS5lcltbaV1dPC1yZWNvUmRzZXEuREUoZGUsZHQsdG9vbD0nZWRnZVInKQogICAgcmVjMS5kZS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmRlW1tpXV0sIHAgPSAwLjEpCiAgICByZWMxLmVyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZXJbW2ldXSwgcCA9IDAuMSkKICAgIHJlYzEuREVHLmxpc3RbW2ldXTwtcmVjMS5kZVtbaV1dW2ludGVyc2VjdChyZWMxLmRlLmdlbmVzLCByZWMxLmVyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzEuZGVbW2ldXSkpKSldCiAgICByZWMxLmdsb2JhbC5ERUc8LSBjKHJlYzEuZ2xvYmFsLkRFRywgYXMuY2hhcmFjdGVyKGludGVyc2VjdChyZWMxLmRlLmdlbmVzLHJlYzEuZXIuZ2VuZXMpKSkKICB9Cn0KcmVjMS5nbG9iYWwuREVHMTwtYXMuZGF0YS5mcmFtZSh0YWJsZShyZWMxLmdsb2JhbC5ERUcpW29yZGVyKHRhYmxlKHJlYzEuZ2xvYmFsLkRFRyksIGRlY3JlYXNpbmcgPSBUUlVFKV0pCnJlYzEuZ2xvYmFsLkRFRzI8LWFzLmRhdGEuZnJhbWUodGFibGUocmVjMS5nbG9iYWwuREVHKVtvcmRlcih0YWJsZShyZWMxLmdsb2JhbC5ERUcpLCBkZWNyZWFzaW5nID0gVFJVRSldKQpjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcxKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcyKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpyZWMxLmdsb2JhbC5ERUcxJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzEuZ2xvYmFsLkRFRzEkZ2VuZUlEKQpyZWMxLmdsb2JhbC5ERUcyJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzEuZ2xvYmFsLkRFRzIkZ2VuZUlEKQpmb3IoaSBpbiB1bmlxdWUocmVjMWQkRGF5KSl7CiAgaWYoaTw4KXsKICByZWMxLmdsb2JhbC5ERUcxJFYxPC1yZWMxLmRlW1tpXV1bcmVjMS5nbG9iYWwuREVHMSRnZW5lSUQsNF0KICBjb2xuYW1lcyhyZWMxLmdsb2JhbC5ERUcxKVtuY29sKHJlYzEuZ2xvYmFsLkRFRzEpXTwtcGFzdGUwKCJsb2cyRm9sZENoYW5nZV9GQ19kYXkiLCBpKQogIHJlYzEuZ2xvYmFsLkRFRzIkVjE8LXJlYzEuZGVbW2ldXVtyZWMxLmdsb2JhbC5ERUcyJGdlbmVJRCw4XQogIGNvbG5hbWVzKHJlYzEuZ2xvYmFsLkRFRzIpW25jb2wocmVjMS5nbG9iYWwuREVHMildPC1wYXN0ZTAoImxvZzJGb2xkQ2hhbmdlX1NDX2RheSIsIGkpCgogIH0KfQpyZWMxLmdsb2JhbC5ERUcxJGxvZzJGb2xkQ2hhbmdlLm1heDwtcmVjMS5nbG9iYWwuREVHMVssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzEpXVtjYmluZCgxOm5yb3cocmVjMS5nbG9iYWwuREVHMVssMzpuY29sKHJlYzEuZ2xvYmFsLkRFRzEpXSksIG1heC5jb2wocmVwbGFjZSh4IDwtIGFicyhyZWMxLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMSldKSwgaXMubmEoeCksIC1JbmYpKSldCnJlYzEuZ2xvYmFsLkRFRzIkbG9nMkZvbGRDaGFuZ2UubWF4PC1yZWMxLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMildW2NiaW5kKDE6bnJvdyhyZWMxLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMS5nbG9iYWwuREVHMildKSwgbWF4LmNvbChyZXBsYWNlKHggPC0gYWJzKHJlYzEuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMxLmdsb2JhbC5ERUcyKV0pLCBpcy5uYSh4KSwgLUluZikpKV0KCnJlYzEuZ2xvYmFsLkRFRzwtcmVjMS5nbG9iYWwuREVHMVssYygxLDIsbmNvbChyZWMxLmdsb2JhbC5ERUcxKSldCnJlYzEuZ2xvYmFsLkRFRyRWMTwtcmVjMS5nbG9iYWwuREVHMlssbmNvbChyZWMxLmdsb2JhbC5ERUcxKV0KY29sbmFtZXMocmVjMS5nbG9iYWwuREVHKVszXTwtImxvZzJGb2xkQ2hhbmdlLm1heF9GQyIKY29sbmFtZXMocmVjMS5nbG9iYWwuREVHKVs0XTwtImxvZzJGb2xkQ2hhbmdlLm1heF9TQyIKCmBgYAoKCiMjIFBsb3R0aW5nIGluZGl2aWR1YWwgREVHczoKICBXZSBwbG90IHZzdC10cmFuc2Zvcm1lZCBnZW5vbWUtbWFwcGluZyBzcGFjZXIgY291bnRzIGZvciA2IGdlbmVzIGluIHRoZSBnbnRSIHBhdGh3YXkgZm9yIGNob3cgYW5kIHN0YXJjaCBmZWQgbWljZSBvbiBkYXkgNy4KICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpnbnRSX2dlbmVzPC1jKCdlZGEnLCdlZGQnLCAnZ250VCcsICdrZGdUJywgJ2dudFUnLCAnZ250SycpCmRlPC1yZWMxZF9kYXk3W3JlYzFkX2RheTckRGlldCE9J0ZhdCcsXQpkdDwtcmVjMV9kYXk3dGZbZ250Ul9nZW5lcywgcm93bmFtZXMoZGUpXQpyZWMxLmdudFIucGxvdC5kZjwtZGF0YS5mcmFtZShEaWV0PWRlJERpZXQsIHQoZHQpKQpyZWMxLmdudFIucGxvdC5kZjwtbWVsdChyZWMxLmdudFIucGxvdC5kZiwgaWQudmFycyA9ICdEaWV0JykKcmVjMS5nbnRSLnBsb3Q8LWdncGxvdChyZWMxLmdudFIucGxvdC5kZiwgYWVzKHk9dmFsdWUsIHg9dmFyaWFibGUsIGZpbGw9RGlldCwgY29sb3I9J2JsYWNrJykpK2dlb21fYm94cGxvdChzaXplPTAuMjQsIG91dGxpZXIuc2l6ZT0wKStnZW9tX3BvaW50KHNpemU9MC40OCwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjc1KSkrdGhlbWVfcHViK3lsYWIoImdlbmUtYWxpZ25pbmcgc3BhY2VyIGNvdW50cyAodnN0LXRyYW5zZm9ybWVkKSIpK3hsYWIoImdlbmUiKStnZ3RpdGxlKCJSZWNvcmQtc2VxIGNvdW50cyBvbiBkYXkgNyBmb3IgZ250UiBnZW5lIikrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXRbYygxLDMpXSkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIpLCBndWlkZT0nbm9uZScpCnJlYzEuZ250Ui5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgoKYGBgCiMjIEhlYXRtYXAgZm9yIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgREVHcyBvbiBkYXkgNwogIFdlIHBsb3QgaGVhdG1hcHMgc2hvd2luZyBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBzYW1wbGVzIHVzaW5nIGRldGVjdGVkIERFIGdlbmVzIGZvciBib3RoIFJlY29yZC1zZXEgYW5kIFJOQS1zZXEgb24gZGF5IDcuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMocmVjMS5ERUcpKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMocmVjMS5ERUcpKSkKaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgcmVjMS5ERUc8LXJlYzEuREVHWy1yaWJvc29tYWwsXQp9CmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocmVjMV9kYXk3dGZbcmVjMS5ERUckZ2VuZUlELF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMS5kYXk3PC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSByZWMxZF9kYXk3WywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gVFJVRSwgdHJlZWhlaWdodF9yb3cgPSAwLCBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSAiY2FuYmVycmEiLCB0cmVlaGVpZ2h0X2NvbCA9IDUsc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBzaG93X3Jvd25hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgd2lkdGg9Mi4yOCwgaGVpZ2h0PTIuMjgsIG1haW49J1JlY29yZC1zZXEgREVHcyBkYXkgNycpCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhybmExLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhybmExLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICBybmExLkRFRzwtcm5hMS5ERUdbLXJpYm9zb21hbCxdCn0KY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmExX3RmW3JuYTEuREVHJGdlbmVJRCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJuYTEuZGF5NzwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcm5hMWRbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCB0cmVlaGVpZ2h0X3JvdyA9IDAsIHRyZWVoZWlnaHRfY29sID0gNSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLHNob3dfcm93bmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCB3aWR0aD0yLjI4LCBoZWlnaHQ9Mi4yOCwgbWFpbj0nUk5BLXNlcSBERUdzIGRheSA3JykKYGBgCgojIyBWb2xjYW5vIHBsb3RzIGZvciBSZWNvcmQtc2VxIERFR3MKCiAgV2UgcGVyZm9ybSBwYWlyd2lzZSBERSBhbmFseXNpcyB1c2luZyBERVNlcTIgYW5kIGVkZ2VSIHRvIGlkZW50aWZ5IGxvZzJGQyBhbmQgcC1hZGogdmFsdWVzIGZvciBlYWNoIGRpZXQgcGFpciBvbiBkYXkgNywgYW5kIHBsb3Qgdm9sY2Fub2VzIChsb2cyRkM+MS41LCBwYWRqPDAuMSkKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTN9CmxldmVsczwtc29ydCh1bmlxdWUocmVjMWRfZGF5N1ssMV0pKQpwYWlyd2lzZS5jb21ibzwtY29tYm4obGV2ZWxzLCAyKQpjb2xvci5jb21ibzwtY29tYm4oY29sb3VyX2NvZGUkRGlldCwgMikKcmVjMS5kZS52YWxzPC1saXN0KCkKcmVjMS5lZC52YWxzPC1saXN0KCkKdm9sLnBsb3RzPC1saXN0KCkKREVHPC1saXN0KCkKZm9yKGkgaW4gMTpkaW0ocGFpcndpc2UuY29tYm8pWzJdKXsKICBkczwtcmVjMWRfZGF5N1t3aGljaChyZWMxZF9kYXk3WywxXSVpbiVwYWlyd2lzZS5jb21ib1ssaV0pLF0KICBkcyREaWV0PC1hcy5jaGFyYWN0ZXIoZHMkRGlldCkKICBkdDwtcmVjMV9kYXk3Wyxyb3duYW1lcyhkcyldCiAgZHRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKGR0LGRzKQogIHJlYzEuZGUudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnREVTZXEyJykKICByZWMxLmVkLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ2VkZ2VSJykKICByb3duYW1lcyhyZWMxLmRlLnZhbHNbW2ldXSkgPC0gcmVjMS5kZS52YWxzW1tpXV0kZ2VuZUlECiAgcm93bmFtZXMocmVjMS5lZC52YWxzW1tpXV0pIDwtIHJlYzEuZWQudmFsc1tbaV1dJGdlbmVJRAogIGRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZGUudmFsc1tbaV1dLCBwID0gMC4xKQogIGVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZWQudmFsc1tbaV1dLCBwID0gMC4xKQogIERFR1tbaV1dPC1kYXRhLmZyYW1lKHJvdy5uYW1lcz1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksbG9nMkZvbGRjaGFuZ2U9cmVjMS5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDRdLCBwYWRqPXJlYzEuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA3XSkKICByaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMoREVHW1tpXV0pKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMoREVHW1tpXV0pKSkKICBpZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICAgICAgREVHW1tpXV08LURFR1tbaV1dWy1yaWJvc29tYWwsXQogIH0KICBERUdbW2ldXSRnZW5lSUQ8LWFzLmNoYXJhY3RlcihERUdbW2ldXSRnZW5lSUQpCiAgcmVjMS5kZS52YWxzW1tpXV08LXJlYzEuZGUudmFsc1tbaV1dW2NvbXBsZXRlLmNhc2VzKHJlYzEuZGUudmFsc1tbaV1dKSxdCiAgcmVjMS5kZS52YWxzW1tpXV0kR3JvdXA8LSdOb25lJwogIHJlYzEuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChyZWMxLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUmcmVjMS5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSkKICByZWMxLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpJnJlYzEuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKQogcmVjMS5kZS52YWxzW1tpXV0kR3JvdXA8LWZhY3RvcihyZWMxLmRlLnZhbHNbW2ldXSRHcm91cCwgbGV2ZWxzID0gYyhwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pKSwgcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pLCAnTm9uZScgKSkKICByZWMxLmRlLnZhbHNbW2ldXSRsYWJlbDwtRkFMU0UKICBtMTwtcmVjMS5kZS52YWxzW1tpXV1bcmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41LCAnZ2VuZUlEJ11bMToxMF0KICBtMjwtcmVjMS5kZS52YWxzW1tpXV1bcmVjMS5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U8KC0xLjUpLCAnZ2VuZUlEJ11bMToxMF0KICBtPC13aGljaChyZWMxLmRlLnZhbHNbW2ldXSRnZW5lSUQlaW4ldW5pb24obTEsbTIpKQogIGZvcihqIGluIG0pewogICAgaWYoYWJzKHJlYzEuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlW2pdKT4xLjUmcmVjMS5kZS52YWxzW1tpXV0kcGFkaltqXTwwLjEpewogICAgICByZWMxLmRlLnZhbHNbW2ldXSRsYWJlbFtqXTwtVFJVRQogICAgfQogIH0KICB2b2wucGxvdHNbW2ldXTwtZ2dwbG90KHJlYzEuZGUudmFsc1tbaV1dLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGNvbG9yPUdyb3VwKSkrc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKGNvbG9yLmNvbWJvWyxpXSwgJ2dyYXk3MCcpKStnZW9tX3BvaW50KHNpemU9MC4yNCkrZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSByZWMxLmRlLnZhbHNbW2ldXVt3aGljaChyZWMxLmRlLnZhbHNbW2ldXSRsYWJlbCksXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBsYWJlbD1nZW5lSUQpLCBzaXplPTEuNzYsIHNob3cubGVnZW5kPUZBTFNFKSt0aGVtZV9wdWIrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMS41LCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IC0xLjUsIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgc2l6ZT0wLjI0KSt4bGFiKCJsb2cyIGZvbGQgY2hhbmdlIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKStndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTEuNSkpKQp9Cgp2b2wucGxvdHNbWzFdXSArIHZvbC5wbG90c1tbMl1dICt2b2wucGxvdHNbWzNdXSArIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKStwbG90X2xheW91dChuY29sID0gMikKCmBgYAoKIyMgQ2x1c3RlcmluZyBvbiBmaW5hbCBkYXkgb2YgZXhwZXJpbWVudAogIFdlIHdhbnQgdG8gY2hlY2sgd2hldGhlciBpbmZvcm1hdGlvbiBhYm91dCBkaWV0IGdyb3VwcyBwcmlvciB0byBzd2l0Y2ggY2FuIGJlIHJldHJpZXZlZCBhdCBkYXkgMTQgLSBpLmUgNyBkYXlzIGFmdGVyIHRoZSBzd2l0Y2guIEZvciB0aGlzLCB3ZSB1c2UgZGlldC1zaWduYXR1cmUgZ2VuZXMgaWRlbnRpZmllZCBiZWZvcmUgdGhlIHN3aXRjaCB0byBoaWVyYXJjaGljYWxseSBjbHVzdGVyIHRoZSBncm91cHMuIERpZXQtc2lnbmF0dXJlIGdlbmVzIGFyZSBkZWZpbmVkIGhlcmUgYXMgdGhlIHRvcCAxMCBnZW5lcyBieSBudW1iZXIgb2YgZGF5cyAoUmVjb3JkLXNlcSkgb3IgcC1hZGogdmFsdWUgKFJOQS1zZXEpIGRldGVjdGVkIGFzIGVucmljaGVkIChsb2cyRkMgPiAyLjUpIHByaW9yIHRvIHRoZSBzd2l0Y2guIFdlIGNhbiBwZXJmZWN0bHkgY2xhc3NpZnkgZ3JvdXBzIHVzaW5nIFJlY29yZC1zZXEgZGF0YSwgd2hpbGUgZm9yIFJOQS1zZXEsIHRoZSBncm91cHMgY29udmVyZ2UuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcmVjMS5nbG9iYWwuREVHJGdlbmVJRCksIGdyZXAoInJybCIsIHJlYzEuZ2xvYmFsLkRFRyRnZW5lSUQpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMxLmdsb2JhbC5ERUc8LXJlYzEuZ2xvYmFsLkRFR1stcmlib3NvbWFsLF0KfQpnZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhyZWMxLmdsb2JhbC5ERUdbd2hpY2gocmVjMS5nbG9iYWwuREVHJGxvZzJGb2xkQ2hhbmdlLm1heF9GQz4yLjUpLCAxXVsxOjEwXSwgcmVjMS5nbG9iYWwuREVHW3doaWNoKHJlYzEuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfU0M+Mi41KSwgMV1bMToxMF0scmVjMS5nbG9iYWwuREVHW3doaWNoKHJvd01lYW5zKHJlYzEuZ2xvYmFsLkRFR1ssMzo0XSk8KC0yLjUpKSwgMV1bMToxMF0pKQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJlYzFfZGF5MTR0ZltnZW5lU2hvcnRMaXN0LF0sIDEsIHpzY29yZXN0YW5kYXJkaXplKSkpCmhlYXRtYXAucmVjMTwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcmVjMWRfZGF5MTRbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgdHJlZWhlaWdodF9yb3c9MCwgdHJlZWhlaWdodF9jb2w9NSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgMTQgYmFzZWQgb24gREVHUyBkZXRlY3RlZCBvbiBkYXkgNycpCmhlYXRtYXAuZ2VuZWxpc3Q8LXJlYzFfZGF5MTR0ZltnZW5lU2hvcnRMaXN0LF0KY29sbmFtZXMoaGVhdG1hcC5nZW5lbGlzdCk8LXJlYzFkX2RheTE0JERpZXQKCgpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpnZW5lU2hvcnRMaXN0PC11bmlxdWUoYyhybmExLkRFR1t3aGljaChybmExLkRFRyRsb2cyRm9sZENoYW5nZS5GYXRfdnNfQ2hvdz4yLjUpLCAxXVsxOjEyXSwgcm5hMS5ERUdbd2hpY2gocm5hMS5ERUckbG9nMkZvbGRDaGFuZ2UuU3RhcmNoX3ZzX0Nob3c+Mi41KSwgMV1bMToxMl0scm5hMS5ERUdbd2hpY2gocm93TWVhbnMocm5hMS5ERUdbLDM6NF0pPCgtMi41KSksIDFdWzE6MTJdKSkKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmExX2RheTE0dGZbZ2VuZVNob3J0TGlzdCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpkaGVhdG1hcDwtZGhlYXRtYXBbY29tcGxldGUuY2FzZXMoZGhlYXRtYXApLF0KaGVhdG1hcC5ybmExPC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSBybmExZF9kYXkxNFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCB0cmVlaGVpZ2h0X3Jvdz0wLCAgdHJlZWhlaWdodF9jb2w9NSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJOQS1zZXEgZGF0YSBvbiBkYXkgMTQgYmFzZWQgb24gREVHUyBkZXRlY3RlZCBvbiBkYXkgNycpCgpgYGAKCiMgVHJhbnNpZW50IERpZXQgMgoKICBXZSBub3cgYW5hbHl6ZSBkYXRhIGZvciB0aGUgZXh0ZW5kZWQgdHJhbnNpZW50IGRpZXQgZXhwZXJpbWVudCB3aXRoIDIwIGRheXMuCgojIyBJbXBvcnRpbmcgYW5kIHByZS1wcm9jZXNzaW5nIGRhdGEgZm9yIHRyYW5zaWVudCBkaWV0IDIKICBXZSBpbXBvcnQgdGhlIGRhdGEgbWF0cmljZXMsIGZpbHRlciB0aGVtIGZvciBsb3dseSBleHByZXNzZWQgZ2VuZXMgYXMgd2VsbCBhcyBvdXRsaWVyIHNhbXBsZXMgd2l0aCBsb3cgY3VtdWxhdGl2ZSBjb3VudHMsIGFuZCB1c2UgdnN0IGZyb20gREVTZXEyIHRvIG5vcm1hbGl6ZSBhbmQgdHJhbnNmb3JtIHRoZSBkYXRhLiBXZSBhbHNvIGV4Y2x1ZGUgZGF5MSBmcm9tIHRoZSBhbmFseXNpcyBzaW5jZSB3ZSBoYXZlIGVtcGVyaWNhbGx5IG9ic2VydmVkIHRoYXQgdGhlIGRhdGEgYXJlIG5vaXN5IGZvciB0aGUgZmlyc3QgZGF5IG9mIGNvbG9uaXphdGlvbi4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpyZWMyPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDJfUmVjb3Jkc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpyZWMyZDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQyX1JlY29yZHNlcV9kZXNpZ25tYXRyaXgudHh0IiwgaGVhZGVyID0gVFJVRSkpCkRFTGlzdDwtcmVjb1Jkc2VxLnByZXByb2Nlc3MocmVjMiwgcmVjMmQsIG1pbkNvdW50c1BlclNhbXBsZSA9IDEwMDAwKQpyZWMyPC1ERUxpc3RbWzFdXQpyZWMyZDwtREVMaXN0W1syXV0KcmVjMmQ8LXJlYzJkW3JlYzJkJERheT4xLF0KcmVjMjwtcmVjMlsscm93bmFtZXMocmVjMmQpXQpyZWMyX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzIsIHJlYzJkKQpybmEyPC1hcy5kYXRhLmZyYW1lKHJlYWQudGFibGUoImRhdGEvdHJhbnNpZW50RGlldDJfUk5Bc2VxX2dlbm9tZWFsaWduaW5nLnR4dCIsIGhlYWRlciA9IFRSVUUpKQpybmEyZDwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL3RyYW5zaWVudERpZXQyX1JOQXNlcV9kZXNpZ25tYXRyaXgudHh0IiwgaGVhZGVyID0gVFJVRSkpCnJuYTJkPC1ybmEyZFssMTozXQpERUxpc3Q8LXJlY29SZHNlcS5wcmVwcm9jZXNzKHJuYTIsIHJuYTJkLCBtaW5Db3VudHNQZXJTYW1wbGUgPSAxMDAwMDApCnJuYTI8LURFTGlzdFtbMV1dCnJuYTJkPC1ERUxpc3RbWzJdXQpybmEyX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTIsIHJuYTJkKQpybmFkYXlzPC11bmlxdWUocm5hMmQkRGF5KQpgYGAKCiMjIERhdGEgZXhwbG9yYXRpb24KICBXZSB1c2UgUHJpbmNpcGFsIENvbXBvbmVudCBhbmFseXNpcyBhbmQgVU1BUCBmb3IgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIGFuZCBleHBsb3JpbmcgY2x1c3RlcnMgaW4gYW4gdW5zdXBlcnZpc2VkIGZhc2hpb24gaW4gb3VyIGRhdGEuICBXZSBmaXJzdCBnZW5lcmF0ZSB0aGVzZSBmb3IgdGhlIGVudGlyZSBkYXRhc2V0IGZyb20gUmVjb3JkLXNlcToKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KcmVjMnNkcyA8LSByb3dTZHMoYXMubWF0cml4KHJlYzJfdGYpKQpvIDwtIG9yZGVyKHJlYzJzZHMsIGRlY3JlYXNpbmcgPSBUUlVFKQpyZWMyUENBPC1wcmNvbXAodChyZWMyX3RmW29bMTo1MDBdLF0pKQpwY2Ffc3RhdDwtc3VtbWFyeShyZWMyUENBKQpyZWMyLnBjYV92YXJpYW5jZTwtcGNhX3N0YXQkaW1wb3J0YW5jZVsyLF0KcmVjMlBDQTwtYXMuZGF0YS5mcmFtZShyZWMyUENBJHgpCnJlYzJQQ0EkRGlldDwtcmVjMmQkRGlldApyZWMyUENBJERheTwtZmFjdG9yKHJlYzJkJERheSwgbGV2ZWxzID0gMToyMCkKcmVjMlBDQXBsb3Q8LWdncGxvdChyZWMyUENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygxLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocmVjMi5wY2FfdmFyaWFuY2VbMl0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKSt4bGFiKHBhc3RlMCgiUEMxICgiLCBhcy5jaGFyYWN0ZXIocmVjMi5wY2FfdmFyaWFuY2VbMV0qMTAwKSwgIiUgdmFyaWFuY2UgZXhwbGFpbmVkKSIpKStnZ3RpdGxlKCIgUENBIHBsb3Qgb2YgUmVjb3JkLXNlcSBkYXRhIikKCnJlYzJVTUFQPC11bWFwKHJlYzJQQ0FbLDE6KG5jb2wocmVjMlBDQSktMildLCBjdXN0b20uY29uZmlnKQpyZWMyVU1BUDwtYXMuZGF0YS5mcmFtZShyZWMyVU1BUCRsYXlvdXQpCnJlYzJVTUFQJERheTwtZmFjdG9yKHJlYzJkJERheSwgbGV2ZWxzID0gMToyMCkKcmVjMlVNQVAkRGlldDwtcmVjMmQkRGlldApjb2xuYW1lcyhyZWMyVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJlYzJVTUFQcGxvdDwtZ2dwbG90KHJlYzJVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSZWNvcmQtc2VxIGRhdGEgKGFsbCBkYXlzKSIpCgpyZWMyUENBcGxvdCtyZWMyVU1BUHBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCgogIEZvciBhIGNvbXBhcmFzaW9uIGJldHdlZW4gUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSwgd2UgZXhjbHVkZSB0aGUgZGF5cyBpbiBSZWNvcmQtc2VxIHRoYXQgZG9uJ3QgaGF2ZSBjb3JyZXNwb25kaW5nIFJOQS1zZXEgZGF0YS4gV2UgZmlyc3QgY2hlY2sgaWYgdGhlIGRpZXQgZ3JvdXBzIGNhbiBiZSBjbGFzc2lmaWVkIHVzaW5nIFBDQSBvbiBkYXkgNyAodGhlIGxhc3QgZGF5IHdoZW4gdGhlIG1pY2UgYXJlIGZlZCBkaWZmZXJlbnQgZGlldHMsIGJlZm9yZSBzd2l0Y2hpbmcgYWxsIG1pY2UgdG8gYSAnQ2hvdycgZGlldCkKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KCnJlYzJkX2RheTc8LXJlYzJkW3JlYzJkJERheT09NyxdCnJlYzJfZGF5NzwtcmVjMlssIHJvd25hbWVzKHJlYzJkX2RheTcpXQpyZWMyX2RheTd0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMyX2RheTcsIHJlYzJkX2RheTcpCnJlYzJfZGF5N19zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMyX2RheTd0ZikpCm8gPC0gb3JkZXIocmVjMl9kYXk3X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJlYzJfZGF5N1BDQTwtcHJjb21wKHQocmVjMl9kYXk3dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJlYzJfZGF5N1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpyZWMyX2RheTdQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMl9kYXk3UENBJHgpCnJlYzJfZGF5N1BDQSREaWV0PC1yZWMyZF9kYXk3JERpZXQKcmVjMl9kYXk3UENBJERheTwtZmFjdG9yKHJlYzJkX2RheTckRGF5LCBsZXZlbHMgPSBjKDcpKQpyZWMyX2RheTdQQ0FwbG90PC1nZ3Bsb3QocmVjMl9kYXk3UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSZWNvcmQtc2VxIGRhdGEgb24gZGF5IDcgIikKCnJuYTJkX2RheTc8LXJuYTJkW3JuYTJkJERheT09NyxdCnJuYTJfZGF5Nzwtcm5hMlssIHJvd25hbWVzKHJuYTJkX2RheTcpXQpybmEyX2RheTd0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShybmEyX2RheTcsIHJuYTJkX2RheTcpCnJuYTJfZGF5N19zZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmEyX2RheTd0ZikpCm8gPC0gb3JkZXIocm5hMl9kYXk3X3NkcywgZGVjcmVhc2luZyA9IFRSVUUpCnJuYTJfZGF5N1BDQTwtcHJjb21wKHQocm5hMl9kYXk3dGZbb1sxOjUwMF0sXSkpCnBjYV9zdGF0PC1zdW1tYXJ5KHJuYTJfZGF5N1BDQSkKcGNhX3ZhcmlhbmNlPC1wY2Ffc3RhdCRpbXBvcnRhbmNlWzIsXQpybmEyX2RheTdQQ0E8LWFzLmRhdGEuZnJhbWUocm5hMl9kYXk3UENBJHgpCnJuYTJfZGF5N1BDQSREaWV0PC1ybmEyZF9kYXk3JERpZXQKcm5hMl9kYXk3UENBJERheTwtZmFjdG9yKHJuYTJkX2RheTckRGF5LCBsZXZlbHMgPSBjKDcpKQpybmEyX2RheTdQQ0FwbG90PC1nZ3Bsb3Qocm5hMl9kYXk3UENBLCBhZXMoeD1QQzEsIHk9UEMyLCBmaWxsPURpZXQsICBzaXplPURheSkpK2dlb21fcG9pbnQocGNoPTIxLCBjb2xvdXI9JyMwMDAwMDAnLCBzdHJva2U9MC4yNSkrdGhlbWVfcHViK3NjYWxlX3NpemVfZGlzY3JldGUocmFuZ2U9YygyLDIuNSkpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYXMudmVjdG9yKGNvbG91cl9jb2RlJERpZXQpKStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKSt5bGFiKHBhc3RlMCgiUEMyICgiLCBhcy5jaGFyYWN0ZXIocGNhX3ZhcmlhbmNlWzJdKjEwMCksICIlIHZhcmlhbmNlIGV4cGxhaW5lZCkiKSkreGxhYihwYXN0ZTAoIlBDMSAoIiwgYXMuY2hhcmFjdGVyKHBjYV92YXJpYW5jZVsxXSoxMDApLCAiJSB2YXJpYW5jZSBleHBsYWluZWQpIikpK2dndGl0bGUoIiBQQ0EgcGxvdCBvZiBSTkEtc2VxIGRhdGEgb24gZGF5IDcgIikKCgpyZWMyX2RheTdQQ0FwbG90K3JuYTJfZGF5N1BDQXBsb3QrcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpCgpgYGAKCiAgV2UgdGhlbiBjb21wYXJlIHRoZSB0ZW1wb3JhbCB0cmFqZWN0b3JpZXMgb2YgUmVjb3JkLXNlcSBhbmQgUk5BLXNlcSB1c2luZyBVTUFQcy4gUmVjb3JkLXNlcSBkYXRhIHJldGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgcHJpb3IgZGlldCBncm91cHMgdGlsbCB0aGUgZmluYWwgZGF5LCBhbmQgY2x1c3RlcnMgc3Ryb25nbHkgYmFzZWQgb24gZ3JvdXA7IHdoZXJlYXMgUk5BLXNlcSBkYXRhIGhhcyBhIHByb25vdW5jZWQgdGVtcG9yYWwgY2hhbmdlLCBhbmQgaW5pdGlhbCBjbHVzdGVycyBmb3IgZGlmZmVyZW50IGRpZXQgZ3JvdXBzIHF1aWNrbHkgY29udmVyZ2UuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTN9CnJlYzJkX3JuYTwtcmVjMmRbd2hpY2gocmVjMmQkRGF5JWluJXJuYWRheXMpLF0KcmVjMl9ybmE8LXJlYzJbLCByb3duYW1lcyhyZWMyZF9ybmEpXQpyZWMyX3JuYXRmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJlYzJfcm5hLCByZWMyZF9ybmEpCnJlYzJybmFzZHMgPC0gcm93U2RzKGFzLm1hdHJpeChyZWMyX3JuYXRmKSkKbyA8LSBvcmRlcihyZWMycm5hc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcmVjMnJuYVBDQTwtcHJjb21wKHQocmVjMl9ybmF0ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocmVjMnJuYVBDQSkKcmVjMnJuYS5wY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJlYzJybmFQQ0E8LWFzLmRhdGEuZnJhbWUocmVjMnJuYVBDQSR4KQpyZWMycm5hUENBJERpZXQ8LXJlYzJkX3JuYSREaWV0CnJlYzJybmFQQ0EkRGF5PC1mYWN0b3IocmVjMmRfcm5hJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcmVjMnJuYVVNQVA8LXVtYXAocmVjMnJuYVBDQVssMToobmNvbChyZWMycm5hUENBKS0yKV0sIGN1c3RvbS5jb25maWcpCnJlYzJybmFVTUFQPC1hcy5kYXRhLmZyYW1lKHJlYzJybmFVTUFQJGxheW91dCkKcmVjMnJuYVVNQVAkRGF5PC1mYWN0b3IocmVjMmRfcm5hJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcmVjMnJuYVVNQVAkRGlldDwtcmVjMmRfcm5hJERpZXQKY29sbmFtZXMocmVjMnJuYVVNQVApWzE6Ml08LWMoJ1VNQVAxJywnVU1BUDInKQpyZWMycm5hVU1BUHBsb3Q8LWdncGxvdChyZWMycm5hVU1BUCwgYWVzKHg9VU1BUDEsIHk9VU1BUDIsIGZpbGw9RGlldCwgIHNpemU9RGF5KSkrZ2VvbV9wb2ludChwY2g9MjEsIGNvbG91cj0nIzAwMDAwMCcsIHN0cm9rZT0wLjI1KSt0aGVtZV9wdWIrc2NhbGVfc2l6ZV9kaXNjcmV0ZShyYW5nZT1jKDEsMi41KSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MikpKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBhcy52ZWN0b3IoY29sb3VyX2NvZGUkRGlldCkpK2dndGl0bGUoIlVNQVAgcGxvdCBmb3IgUmVjb3JkLXNlcSBkYXRhIikKCnJuYTJzZHMgPC0gcm93U2RzKGFzLm1hdHJpeChybmEyX3RmKSkKbyA8LSBvcmRlcihybmEyc2RzLCBkZWNyZWFzaW5nID0gVFJVRSkKcm5hMlBDQTwtcHJjb21wKHQocm5hMl90ZltvWzE6NTAwXSxdKSkKcGNhX3N0YXQ8LXN1bW1hcnkocm5hMlBDQSkKcm5hMi5wY2FfdmFyaWFuY2U8LXBjYV9zdGF0JGltcG9ydGFuY2VbMixdCnJuYTJQQ0E8LWFzLmRhdGEuZnJhbWUocm5hMlBDQSR4KQpybmEyUENBJERpZXQ8LXJuYTJkJERpZXQKcm5hMlBDQSREYXk8LWZhY3RvcihybmEyZCREYXksIGxldmVscyA9IHJuYWRheXMpCnJuYTJVTUFQPC11bWFwKHJuYTJQQ0FbLDE6KG5jb2wocm5hMlBDQSktMildLCBjdXN0b20uY29uZmlnKQpybmEyVU1BUDwtYXMuZGF0YS5mcmFtZShybmEyVU1BUCRsYXlvdXQpCnJuYTJVTUFQJERheTwtZmFjdG9yKHJuYTJkJERheSwgbGV2ZWxzID0gcm5hZGF5cykKcm5hMlVNQVAkRGlldDwtcm5hMmQkRGlldApjb2xuYW1lcyhybmEyVU1BUClbMToyXTwtYygnVU1BUDEnLCdVTUFQMicpCnJuYTJVTUFQcGxvdDwtZ2dwbG90KHJuYTJVTUFQLCBhZXMoeD1VTUFQMSwgeT1VTUFQMiwgZmlsbD1EaWV0LCAgc2l6ZT1EYXkpKStnZW9tX3BvaW50KHBjaD0yMSwgY29sb3VyPScjMDAwMDAwJywgc3Ryb2tlPTAuMjUpK3RoZW1lX3B1YitzY2FsZV9zaXplX2Rpc2NyZXRlKHJhbmdlPWMoMSwyLjUpKStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIHNpemU9MC4yNCkrIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0yKSkpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0KSkrZ2d0aXRsZSgiVU1BUCBwbG90IGZvciBSTkEtc2VxIGRhdGEiKQpyZWMycm5hVU1BUHBsb3Qrcm5hMlVNQVBwbG90K3Bsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gJ0EnKQoKYGBgCgoKIyMgRGlzY292ZXJ5IG9mIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcyk6CiAgV2UgaWRlbnRpZnkgREVHcyBmb3IgZGF5IDcgKHNpbmNlIHRoaXMgaXMgdGhlIGZpbmFsIGRheSB3aGVuIHRoZSBtaWNlIGFyZSBmZWQgc2VwYXJhdGUgZGlldHMsIGFuZCBSTkEtc2VxIGlzIGFsc28gYXZhaWxhYmxlIGZvciB0aGlzIGRheSkuIFdlIGRlZmluZSBERUdzIGFzIGdlbmVzIGlkZW50aWZpZWQgdG8gYmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgdXNpbmcgYSB0aHJlc2hvbGQgKHBhZGogPCAwLjA1KSBieSBib3RoIERFU2VxMiBhbmQgZWRnZVIgKG11bHRpcGxlIHRlc3RpbmcsIHNpbmNlIHdlIGhhdmUgMyBncm91cHMpLiAgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyZWMyLmRlc2VxPC1yZWNvUmRzZXEuREUocmVjMl9kYXk3LCByZWMyZF9kYXk3LCB0b29sPSdERVNlcTInKQpyZWMyLmVkZ2VyPC1yZWNvUmRzZXEuREUocmVjMl9kYXk3LCByZWMyZF9kYXk3LCB0b29sPSdlZGdlUicpCnJlYzIuZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5kZXNlcSwgcCA9IDAuMDUpCnJlYzIuZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5lZGdlciwgcCA9IDAuMDUpCnJlYzIuREVHPC1yZWMyLmRlc2VxW2ludGVyc2VjdChyZWMyLmRlc2VxLmdlbmVzLCByZWMyLmVkZ2VyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzIuZGVzZXEpKSkpXQpyZWMyLkRFRyRnZW5lSUQ8LWFzLmNoYXJhY3RlcihyZWMyLkRFRyRnZW5lSUQpCnJlYzIuREVHPC1yZWMyLkRFR1tvcmRlcihyZWMyLkRFRyRwYWRqKSxdCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhyZWMyLkRFRykpLCBncmVwKCJycmwiLCByb3duYW1lcyhyZWMyLkRFRykpKQppZihsZW5ndGgocmlib3NvbWFsKT4wKXsKICByZWMyLkRFRzwtcmVjMi5ERUdbLXJpYm9zb21hbCxdCn0Kcm5hMi5kZXNlcTwtcmVjb1Jkc2VxLkRFKHJuYTJfZGF5Nywgcm5hMmRfZGF5NywgdG9vbD0nREVTZXEyJykKcm5hMi5lZGdlcjwtcmVjb1Jkc2VxLkRFKHJuYTJfZGF5Nywgcm5hMmRfZGF5NywgdG9vbD0nZWRnZVInKQpybmEyLmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZGVzZXEsIHAgPSAwLjA1KQpybmEyLmVkZ2VyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJuYTIuZWRnZXIsIHAgPSAwLjA1KQpybmEyLkRFRzwtcm5hMi5kZXNlcVtpbnRlcnNlY3Qocm5hMi5kZXNlcS5nZW5lcywgcm5hMi5lZGdlci5nZW5lcyksIGMoMSw3LCB3aGljaChncmVwbCgnbG9nMkZvbGRDaGFuZ2UnLCBjb2xuYW1lcyhybmEyLmRlc2VxKSkpKV0Kcm5hMi5ERUckZ2VuZUlEPC1hcy5jaGFyYWN0ZXIocm5hMi5ERUckZ2VuZUlEKQpybmEyLkRFRzwtcm5hMi5ERUdbb3JkZXIocm5hMi5ERUckcGFkaiksXQpyaWJvc29tYWw8LWMoZ3JlcCgicnJzIiwgcm93bmFtZXMocm5hMi5ERUcpKSwgZ3JlcCgicnJsIiwgcm93bmFtZXMocm5hMi5ERUcpKSkKaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgcm5hMi5ERUc8LXJuYTIuREVHWy1yaWJvc29tYWwsXQp9CnJlYzIubm92ZWw8LXJlYzIuREVHWy13aGljaChyZWMyLkRFRyRnZW5lSUQlaW4lcm5hMi5ERUckZ2VuZUlEKSxdCmBgYAogIAogIFdlIGFsc28gbG9vayBmb3IgREUgZ2VuZXMgb3ZlciBkYXlzIDItNyBpbiBSZWNvcmQtc2VxIHVzaW5nIGEgbG9vc2VyIGNvbmZpZGVuY2UgdGhyZXNob2xkIChwYWRqIDwwLjEpIHRvIGlkZW50aWZ5IGNvbnNpc3RlbnQgZGlldC1zaWduYXR1cmUgZ2VuZXMuCiAgICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmVjMi5ERUcubGlzdDwtbGlzdCgpCnJlYzIuZXI8LWxpc3QoKQpyZWMyLmRlPC1saXN0KCkKcmVjMi5nbG9iYWwuREVHPC1jKCkKZm9yKGkgaW4gdW5pcXVlKHJlYzJkJERheSkpewogIGlmKGk8OCl7CiAgICBkdDwtcmVjMmRbd2hpY2gocmVjMmQkRGF5PT1pKSwgMSwgZHJvcD1GQUxTRV0KICAgIGR0JERpZXQ8LWZhY3RvcihkdCREaWV0KQogICAgZGU8LXJlYzJbLHdoaWNoKGNvbG5hbWVzKHJlYzIpJWluJXJvd25hbWVzKGR0KSldCiAgICByZWMyLmRlW1tpXV08LXJlY29SZHNlcS5ERShkZSxkdCx0b29sPSdERVNlcTInKQogICAgcmVjMi5lcltbaV1dPC1yZWNvUmRzZXEuREUoZGUsZHQsdG9vbD0nZWRnZVInKQogICAgcmVjMi5kZS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmRlW1tpXV0sIHAgPSAwLjEpCiAgICByZWMyLmVyLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZXJbW2ldXSwgcCA9IDAuMSkKICAgIHJlYzIuREVHLmxpc3RbW2ldXTwtcmVjMi5kZVtbaV1dW2ludGVyc2VjdChyZWMyLmRlLmdlbmVzLCByZWMyLmVyLmdlbmVzKSwgYygxLDcsIHdoaWNoKGdyZXBsKCdsb2cyRm9sZENoYW5nZScsIGNvbG5hbWVzKHJlYzIuZGVbW2ldXSkpKSldCiAgICByZWMyLmdsb2JhbC5ERUc8LSBjKHJlYzIuZ2xvYmFsLkRFRywgYXMuY2hhcmFjdGVyKGludGVyc2VjdChyZWMyLmRlLmdlbmVzLHJlYzIuZXIuZ2VuZXMpKSkKICB9Cn0KcmVjMi5nbG9iYWwuREVHMTwtYXMuZGF0YS5mcmFtZSh0YWJsZShyZWMyLmdsb2JhbC5ERUcpW29yZGVyKHRhYmxlKHJlYzIuZ2xvYmFsLkRFRyksIGRlY3JlYXNpbmcgPSBUUlVFKV0pCnJlYzIuZ2xvYmFsLkRFRzI8LWFzLmRhdGEuZnJhbWUodGFibGUocmVjMi5nbG9iYWwuREVHKVtvcmRlcih0YWJsZShyZWMyLmdsb2JhbC5ERUcpLCBkZWNyZWFzaW5nID0gVFJVRSldKQpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcxKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcyKTwtYygiZ2VuZUlEIiwgImRheXNfREUiKQpyZWMyLmdsb2JhbC5ERUcxJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzIuZ2xvYmFsLkRFRzEkZ2VuZUlEKQpyZWMyLmdsb2JhbC5ERUcyJGdlbmVJRDwtYXMuY2hhcmFjdGVyKHJlYzIuZ2xvYmFsLkRFRzIkZ2VuZUlEKQpmb3IoaSBpbiB1bmlxdWUocmVjMmQkRGF5KSl7CiAgaWYoaTw4KXsKICByZWMyLmdsb2JhbC5ERUcxJFYxPC1yZWMyLmRlW1tpXV1bcmVjMi5nbG9iYWwuREVHMSRnZW5lSUQsNF0KICBjb2xuYW1lcyhyZWMyLmdsb2JhbC5ERUcxKVtuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXTwtcGFzdGUwKCJsb2cyRm9sZENoYW5nZV9GQ19kYXkiLCBpKQogIHJlYzIuZ2xvYmFsLkRFRzIkVjE8LXJlYzIuZGVbW2ldXVtyZWMyLmdsb2JhbC5ERUcyJGdlbmVJRCw4XQogIGNvbG5hbWVzKHJlYzIuZ2xvYmFsLkRFRzIpW25jb2wocmVjMi5nbG9iYWwuREVHMildPC1wYXN0ZTAoImxvZzJGb2xkQ2hhbmdlX1NDX2RheSIsIGkpCgogIH0KfQpyZWMyLmdsb2JhbC5ERUcxJGxvZzJGb2xkQ2hhbmdlLm1heDwtcmVjMi5nbG9iYWwuREVHMVssMzpuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXVtjYmluZCgxOm5yb3cocmVjMi5nbG9iYWwuREVHMVssMzpuY29sKHJlYzIuZ2xvYmFsLkRFRzEpXSksIG1heC5jb2wocmVwbGFjZSh4IDwtIGFicyhyZWMyLmdsb2JhbC5ERUcxWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMSldKSwgaXMubmEoeCksIC1JbmYpKSldCnJlYzIuZ2xvYmFsLkRFRzIkbG9nMkZvbGRDaGFuZ2UubWF4PC1yZWMyLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMildW2NiaW5kKDE6bnJvdyhyZWMyLmdsb2JhbC5ERUcyWywzOm5jb2wocmVjMi5nbG9iYWwuREVHMildKSwgbWF4LmNvbChyZXBsYWNlKHggPC0gYWJzKHJlYzIuZ2xvYmFsLkRFRzJbLDM6bmNvbChyZWMyLmdsb2JhbC5ERUcyKV0pLCBpcy5uYSh4KSwgLUluZikpKV0KCnJlYzIuZ2xvYmFsLkRFRzwtcmVjMi5nbG9iYWwuREVHMVssYygxLDIsbmNvbChyZWMyLmdsb2JhbC5ERUcxKSldCnJlYzIuZ2xvYmFsLkRFRyRWMTwtcmVjMi5nbG9iYWwuREVHMlssbmNvbChyZWMyLmdsb2JhbC5ERUcyKV0KY29sbmFtZXMocmVjMi5nbG9iYWwuREVHKVszXTwtImxvZzJGb2xkQ2hhbmdlLm1heF9GQyIKY29sbmFtZXMocmVjMi5nbG9iYWwuREVHKVs0XTwtImxvZzJGb2xkQ2hhbmdlLm1heF9TQyIKCmBgYAogCiAKIyMgUGxvdHRpbmcgaW5kaXZpZHVhbCBERUdzOgogIFdlIHBsb3QgdnN0LXRyYW5zZm9ybWVkIGdlbm9tZS1tYXBwaW5nIHNwYWNlciBjb3VudHMgZm9yIDUgZ2VuZXMgaW4gdGhlIGdudFIgcGF0aHdheSBmb3IgY2hvdyBhbmQgc3RhcmNoIGZlZCBtaWNlIG9uIGRheSA3LgogIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTIuNSwgZmlnLndpZHRoPTN9CmdudFJfZ2VuZXM8LWMoJ2VkYScsJ2VkZCcsICdnbnRUJywgJ2tkZ1QnLCAnZ250VScsICdnbnRLJykKZGU8LXJlYzJkX2RheTdbcmVjMmRfZGF5NyREaWV0IT0nRmF0JyxdCmR0PC1yZWMyX2RheTd0ZltnbnRSX2dlbmVzLCByb3duYW1lcyhkZSldCnJlYzIuZ250Ui5wbG90LmRmPC1kYXRhLmZyYW1lKERpZXQ9ZGUkRGlldCwgdChkdCkpCnJlYzIuZ250Ui5wbG90LmRmPC1tZWx0KHJlYzIuZ250Ui5wbG90LmRmLCBpZC52YXJzID0gJ0RpZXQnKQpyZWMyLmdudFIucGxvdDwtZ2dwbG90KHJlYzIuZ250Ui5wbG90LmRmLCBhZXMoeT12YWx1ZSwgeD12YXJpYWJsZSwgZmlsbD1EaWV0LCBjb2xvcj0nYmxhY2snKSkrZ2VvbV9ib3hwbG90KHNpemU9MC4yNCwgb3V0bGllci5zaXplPTApK2dlb21fcG9pbnQoc2l6ZT0wLjQ4LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNzUpKSt0aGVtZV9wdWIreWxhYigiZ2VuZS1tYXBwaW5nIHNwYWNlciBjb3VudHMgKHZzdC10cmFuc2Zvcm1lZCkiKSt4bGFiKCJnZW5lIikrZ2d0aXRsZSgiUmVjb3JkLXNlcSBjb3VudHMgb24gZGF5IDcgZm9yIGdudFIgZ2VuZSIpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFzLnZlY3Rvcihjb2xvdXJfY29kZSREaWV0W2MoMSwzKV0pKStzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiYmxhY2siKSwgZ3VpZGU9J25vbmUnKQpyZWMyLmdudFIucGxvdCtwbG90X2Fubm90YXRpb24oKQoKYGBgCgojIyBIZWF0bWFwIGZvciBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIERFR3Mgb24gZGF5IDcKICBXZSBwbG90IGhlYXRtYXBzIHNob3dpbmcgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2Ygc2FtcGxlcyB1c2luZyBkZXRlY3RlZCBERSBnZW5lcyBmb3IgYm90aCBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIG9uIGRheSA3LiAKICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpjb2xzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJkb2RnZXJibHVlNCIsICJ3aGl0ZSIsInZpb2xldHJlZDQiKSkoMjU2KQpkaGVhdG1hcDwtYXMuZGF0YS5mcmFtZSh0KGFwcGx5KHJlYzJfZGF5N3RmW3JlYzIuREVHJGdlbmVJRCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJlYzIuZGF5NzwtcGhlYXRtYXAoZGhlYXRtYXAsIGFubm90YXRpb25fY29sID0gcmVjMmRfZGF5N1ssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IFRSVUUsIHRyZWVoZWlnaHRfcm93ID0gMCwgIHRyZWVoZWlnaHRfY29sID0gNSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLCBzaG93X3Jvd25hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgd2lkdGg9Mi4yOCwgaGVpZ2h0PTIuMjgsIG1haW49J1JlY29yZC1zZXEgREVHcyBkYXkgNycpCmhlYXRtYXAuZ2VuZWxpc3Q8LWhlYXRtYXAucmVjMi5kYXk3JHRyZWVfcm93JGxhYmVsc1toZWF0bWFwLnJlYzIuZGF5NyR0cmVlX3JvdyRvcmRlcl0KaGVhdG1hcC5nZW5lbGlzdDwtcmVjMl9kYXk3dGZbaGVhdG1hcC5nZW5lbGlzdCxdCmNvbG5hbWVzKGhlYXRtYXAuZ2VuZWxpc3QpPC1hcy5jaGFyYWN0ZXIocmVjMmRfZGF5NyREaWV0KQoKY29sczwtIGNvbG9yUmFtcFBhbGV0dGUoYygiZG9kZ2VyYmx1ZTQiLCAid2hpdGUiLCJ2aW9sZXRyZWQ0IikpKDI1NikKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShybmEyX2RheTd0ZltybmEyLkRFRyRnZW5lSUQsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5ybmEyLmRheTc8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJuYTJkX2RheTdbLDEsIGRyb3A9RkFMU0VdLCBhbm5vdGF0aW9uX2NvbG9ycz1jb2xvdXJfY29kZSwgZm9udHNpemUgPSA1LCBmb250c2l6ZV9yb3cgPSA1LCBmb250c2l6ZV9jb2wgPSA1LCBjbHVzdGVyX3Jvd3MgPSBUUlVFLCB0cmVlaGVpZ2h0X3JvdyA9IDAsIHRyZWVoZWlnaHRfY29sID0gNSwgc2hvd19jb2xuYW1lcyA9IEZBTFNFLHNob3dfcm93bmFtZXMgPSBGQUxTRSwgY29sb3IgPSBjb2xzLGZvbnRzaXplX251bWJlcj01LCB3aWR0aD0yLjI4LCBoZWlnaHQ9Mi4yOCwgbWFpbj0nUk5BLXNlcSBERUdzIGRheSA3JykKYGBgCiAgCiMjIFZvbGNhbm8gcGxvdHMgYW5kIGhlYXRtYXBzIGZvciBSZWNvcmQtc2VxIGFuZCBSTkEtc2VxIERFR3M6CldlIHBlcmZvcm0gcGFpcndpc2UgREUgYW5hbHlzaXMgdXNpbmcgREVTZXEyIHRvIGlkZW50aWZ5IGxvZzJGQyBhbmQgcC1hZGogdmFsdWVzIGZvciBlYWNoIGRpZXQgcGFpciBvbiBkYXkgNywgYW5kIHBsb3Qgdm9sY2Fub2VzIChsb2cyRkM+MS41LCBwYWRqPDAuMSkKICAgCiAgUmVjb3JkLXNlcTogCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KbGV2ZWxzPC1zb3J0KHVuaXF1ZShyZWMyZF9kYXk3WywxXSkpCnBhaXJ3aXNlLmNvbWJvPC1jb21ibihsZXZlbHMsIDIpCmNvbG9yLmNvbWJvPC1jb21ibihjb2xvdXJfY29kZSREaWV0LCAyKQpyZWMyLmRlLnZhbHM8LWxpc3QoKQpyZWMyLmVkLnZhbHM8LWxpc3QoKQp2b2wucGxvdHM8LWxpc3QoKQpERUc8LWxpc3QoKQpmb3IoaSBpbiAxOmRpbShwYWlyd2lzZS5jb21ibylbMl0pewogIGRzPC1yZWMyZF9kYXk3W3doaWNoKHJlYzJkX2RheTdbLDFdJWluJXBhaXJ3aXNlLmNvbWJvWyxpXSksXQogIGRzJERpZXQ8LWFzLmNoYXJhY3RlcihkcyREaWV0KQogIGR0PC1yZWMyX2RheTdbLHJvd25hbWVzKGRzKV0KICBkdGY8LXJlY29SZHNlcS50cmFuc2Zvcm0oZHQsZHMpCiAgcmVjMi5kZS52YWxzW1tpXV0gPC0gcmVjb1Jkc2VxLkRFKGR0LCBkcywgdG9vbCA9ICdERVNlcTInKQogIHJlYzIuZWQudmFsc1tbaV1dIDwtIHJlY29SZHNlcS5ERShkdCwgZHMsIHRvb2wgPSAnZWRnZVInKQogIHJvd25hbWVzKHJlYzIuZGUudmFsc1tbaV1dKSA8LSByZWMyLmRlLnZhbHNbW2ldXSRnZW5lSUQKICByb3duYW1lcyhyZWMyLmVkLnZhbHNbW2ldXSkgPC0gcmVjMi5lZC52YWxzW1tpXV0kZ2VuZUlECiAgZGVzZXEuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5kZS52YWxzW1tpXV0sIHAgPSAwLjEpCiAgZWRnZXIuZ2VuZXM8LXJlY29SZHNlcS5maWx0ZXJERUcocmVjMi5lZC52YWxzW1tpXV0sIHAgPSAwLjEpCiAgREVHW1tpXV08LWRhdGEuZnJhbWUocm93Lm5hbWVzPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGdlbmVJRD1pbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxsb2cyRm9sZGNoYW5nZT1yZWMyLmRlLnZhbHNbW2ldXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgNF0sIHBhZGo9cmVjMi5kZS52YWxzW1tpXV1baW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIDddKQogIHJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByb3duYW1lcyhERUdbW2ldXSkpLCBncmVwKCJycmwiLCByb3duYW1lcyhERUdbW2ldXSkpKQogIGlmKGxlbmd0aChyaWJvc29tYWwpPjApewogICAgICBERUdbW2ldXTwtREVHW1tpXV1bLXJpYm9zb21hbCxdCiAgfQogIERFR1tbaV1dJGdlbmVJRDwtYXMuY2hhcmFjdGVyKERFR1tbaV1dJGdlbmVJRCkKICByZWMyLmRlLnZhbHNbW2ldXTwtcmVjMi5kZS52YWxzW1tpXV1bY29tcGxldGUuY2FzZXMocmVjMi5kZS52YWxzW1tpXV0pLF0KICByZWMyLmRlLnZhbHNbW2ldXSRHcm91cDwtJ05vbmUnCiAgcmVjMi5kZS52YWxzW1tpXV0kR3JvdXBbIHdoaWNoKHJlYzIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPjEuNSZyZWMyLmRlLnZhbHNbW2ldXSRwYWRqPDAuMSldPC1wYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIGFzLmNoYXJhY3Rlcihzb3J0KHVuaXF1ZShkcyREaWV0KSlbMl0pKQogIHJlYzIuZGUudmFsc1tbaV1dJEdyb3VwWyB3aGljaChyZWMyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSkmcmVjMi5kZS52YWxzW1tpXV0kcGFkajwwLjEpXTwtcGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBzb3J0KHVuaXF1ZShkcyREaWV0KSlbMV0pCiByZWMyLmRlLnZhbHNbW2ldXSRHcm91cDwtZmFjdG9yKHJlYzIuZGUudmFsc1tbaV1dJEdyb3VwLCBsZXZlbHMgPSBjKHBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgYXMuY2hhcmFjdGVyKHNvcnQodW5pcXVlKGRzJERpZXQpKVsxXSkpLCBwYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIHNvcnQodW5pcXVlKGRzJERpZXQpKVsyXSksICdOb25lJyApKQogIHJlYzIuZGUudmFsc1tbaV1dJGxhYmVsPC1GQUxTRQogIG0xPC1yZWMyLmRlLnZhbHNbW2ldXVtyZWMyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZT4xLjUsICdnZW5lSUQnXVsxOjEwXQogIG0yPC1yZWMyLmRlLnZhbHNbW2ldXVtyZWMyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZTwoLTEuNSksICdnZW5lSUQnXVsxOjEwXQogIG08LXdoaWNoKHJlYzIuZGUudmFsc1tbaV1dJGdlbmVJRCVpbiV1bmlvbihtMSxtMikpCiAgZm9yKGogaW4gbSl7CiAgICBpZihhYnMocmVjMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2Vbal0pPjEuNSZyZWMyLmRlLnZhbHNbW2ldXSRwYWRqW2pdPDAuMSl7CiAgICAgIHJlYzIuZGUudmFsc1tbaV1dJGxhYmVsW2pdPC1UUlVFCiAgICB9CiAgfQogIHZvbC5wbG90c1tbaV1dPC1nZ3Bsb3QocmVjMi5kZS52YWxzW1tpXV0sIGFlcyggeD1sb2cyRm9sZENoYW5nZSwgeT0oLWxvZzEwKHBhZGopKSwgY29sb3I9R3JvdXApKStzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoY29sb3IuY29tYm9bLGldLCAnZ3JheTcwJykpK2dlb21fcG9pbnQoc2l6ZT0wLjI0KStnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHJlYzIuZGUudmFsc1tbaV1dW3doaWNoKHJlYzIuZGUudmFsc1tbaV1dJGxhYmVsKSxdLCBhZXMoIHg9bG9nMkZvbGRDaGFuZ2UsIHk9KC1sb2cxMChwYWRqKSksIGxhYmVsPWdlbmVJRCksIHNpemU9MS43Niwgc2hvdy5sZWdlbmQ9RkFMU0UpK3RoZW1lX3B1YitnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxLjUsIHNpemU9MC4yNCkrZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLTEuNSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBzaXplPTAuMjQpK3hsYWIoImxvZzIgZm9sZCBjaGFuZ2UiKSt5bGFiKCItbG9nMTAgcC1hZGp1c3RlZCB2YWx1ZSIpK2d1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MS41KSkpCn0KCnZvbC5wbG90c1tbMV1dICsgdm9sLnBsb3RzW1syXV0gK3ZvbC5wbG90c1tbM11dICsgcGxvdF9hbm5vdGF0aW9uKHRhZ19sZXZlbHMgPSAnQScpK3Bsb3RfbGF5b3V0KG5jb2wgPSAyKQoKYGBgCiAgCiAgUk5BLXNlcToKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpsZXZlbHM8LXNvcnQodW5pcXVlKHJuYTJkX2RheTdbLDFdKSkKcGFpcndpc2UuY29tYm88LWNvbWJuKGxldmVscywgMikKY29sb3IuY29tYm88LWNvbWJuKGNvbG91cl9jb2RlJERpZXQsIDIpCnJuYTIuZGUudmFsczwtbGlzdCgpCnJuYTIuZWQudmFsczwtbGlzdCgpCnZvbC5wbG90czwtbGlzdCgpCkRFRzwtbGlzdCgpCmZvcihpIGluIDE6ZGltKHBhaXJ3aXNlLmNvbWJvKVsyXSl7CiAgZHM8LXJuYTJkX2RheTdbd2hpY2gocm5hMmRfZGF5N1ssMV0laW4lcGFpcndpc2UuY29tYm9bLGldKSxdCiAgZHMkRGlldDwtYXMuY2hhcmFjdGVyKGRzJERpZXQpCiAgZHQ8LXJuYTJfZGF5N1sscm93bmFtZXMoZHMpXQogIGR0ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShkdCxkcykKICBybmEyLmRlLnZhbHNbW2ldXSA8LSByZWNvUmRzZXEuREUoZHQsIGRzLCB0b29sID0gJ0RFU2VxMicpCiAgcm5hMi5lZC52YWxzW1tpXV0gPC0gcmVjb1Jkc2VxLkRFKGR0LCBkcywgdG9vbCA9ICdlZGdlUicpCiAgcm93bmFtZXMocm5hMi5kZS52YWxzW1tpXV0pIDwtIHJuYTIuZGUudmFsc1tbaV1dJGdlbmVJRAogIHJvd25hbWVzKHJuYTIuZWQudmFsc1tbaV1dKSA8LSBybmEyLmVkLnZhbHNbW2ldXSRnZW5lSUQKICBkZXNlcS5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhybmEyLmRlLnZhbHNbW2ldXSwgcCA9IDAuMSkKICBlZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhybmEyLmVkLnZhbHNbW2ldXSwgcCA9IDAuMSkKICBERUdbW2ldXTwtZGF0YS5mcmFtZShyb3cubmFtZXM9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksZ2VuZUlEPWludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLGxvZzJGb2xkY2hhbmdlPXJuYTIuZGUudmFsc1tbaV1dW2ludGVyc2VjdChkZXNlcS5nZW5lcywgZWRnZXIuZ2VuZXMpLCA0XSwgcGFkaj1ybmEyLmRlLnZhbHNbW2ldXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSwgN10pCiAgcmlib3NvbWFsPC1jKGdyZXAoInJycyIsIHJvd25hbWVzKERFR1tbaV1dKSksIGdyZXAoInJybCIsIHJvd25hbWVzKERFR1tbaV1dKSkpCiAgaWYobGVuZ3RoKHJpYm9zb21hbCk+MCl7CiAgICAgIERFR1tbaV1dPC1ERUdbW2ldXVstcmlib3NvbWFsLF0KICB9CiAgREVHW1tpXV0kZ2VuZUlEPC1hcy5jaGFyYWN0ZXIoREVHW1tpXV0kZ2VuZUlEKQogIHJuYTIuZGUudmFsc1tbaV1dPC1ybmEyLmRlLnZhbHNbW2ldXVtjb21wbGV0ZS5jYXNlcyhybmEyLmRlLnZhbHNbW2ldXSksXQogIHJuYTIuZGUudmFsc1tbaV1dJEdyb3VwPC0nTm9uZScKICBybmEyLmRlLnZhbHNbW2ldXSRHcm91cFsgd2hpY2gocm5hMi5kZS52YWxzW1tpXV0kbG9nMkZvbGRDaGFuZ2U+MS41JnJuYTIuZGUudmFsc1tbaV1dJHBhZGo8MC4xKV08LXBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgYXMuY2hhcmFjdGVyKHNvcnQodW5pcXVlKGRzJERpZXQpKVsyXSkpCiAgcm5hMi5kZS52YWxzW1tpXV0kR3JvdXBbIHdoaWNoKHJuYTIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPCgtMS41KSZybmEyLmRlLnZhbHNbW2ldXSRwYWRqPDAuMSldPC1wYXN0ZTAoInVwcmVndWxhdGVkLmluLiIsIHNvcnQodW5pcXVlKGRzJERpZXQpKVsxXSkKIHJuYTIuZGUudmFsc1tbaV1dJEdyb3VwPC1mYWN0b3Iocm5hMi5kZS52YWxzW1tpXV0kR3JvdXAsIGxldmVscyA9IGMocGFzdGUwKCJ1cHJlZ3VsYXRlZC5pbi4iLCBhcy5jaGFyYWN0ZXIoc29ydCh1bmlxdWUoZHMkRGlldCkpWzFdKSksIHBhc3RlMCgidXByZWd1bGF0ZWQuaW4uIiwgc29ydCh1bmlxdWUoZHMkRGlldCkpWzJdKSwgJ05vbmUnICkpCiAgcm5hMi5kZS52YWxzW1tpXV0kbGFiZWw8LUZBTFNFCiAgbTE8LXJuYTIuZGUudmFsc1tbaV1dW3JuYTIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPjEuNSwgJ2dlbmVJRCddWzE6MTBdCiAgbTI8LXJuYTIuZGUudmFsc1tbaV1dW3JuYTIuZGUudmFsc1tbaV1dJGxvZzJGb2xkQ2hhbmdlPCgtMS41KSwgJ2dlbmVJRCddWzE6MTBdCiAgbTwtd2hpY2gocm5hMi5kZS52YWxzW1tpXV0kZ2VuZUlEJWluJXVuaW9uKG0xLG0yKSkKICBmb3IoaiBpbiBtKXsKICAgIGlmKGFicyhybmEyLmRlLnZhbHNbW2ldXSRsb2cyRm9sZENoYW5nZVtqXSk+MS41JnJuYTIuZGUudmFsc1tbaV1dJHBhZGpbal08MC4xKXsKICAgICAgcm5hMi5kZS52YWxzW1tpXV0kbGFiZWxbal08LVRSVUUKICAgIH0KICB9CiAgdm9sLnBsb3RzW1tpXV08LWdncGxvdChybmEyLmRlLnZhbHNbW2ldXSwgYWVzKCB4PWxvZzJGb2xkQ2hhbmdlLCB5PSgtbG9nMTAocGFkaikpLCBjb2xvcj1Hcm91cCkpK3NjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhjb2xvci5jb21ib1ssaV0sICdncmF5NzAnKSkrZ2VvbV9wb2ludChzaXplPTAuMjQpK2dlb21fdGV4dF9yZXBlbChkYXRhID0gcm5hMi5kZS52YWxzW1tpXV1bd2hpY2gocm5hMi5kZS52YWxzW1tpXV0kbGFiZWwpLF0sIGFlcyggeD1sb2cyRm9sZENoYW5nZSwgeT0oLWxvZzEwKHBhZGopKSwgbGFiZWw9Z2VuZUlEKSwgc2l6ZT0xLjc2LCBzaG93LmxlZ2VuZD1GQUxTRSkrdGhlbWVfcHViK2dlb21fdmxpbmUoeGludGVyY2VwdCA9IDEuNSwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAtMS41LCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIHNpemU9MC4yNCkreGxhYigibG9nMiBmb2xkIGNoYW5nZSIpK3lsYWIoIi1sb2cxMCBwLWFkanVzdGVkIHZhbHVlIikrZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0xLjUpKSkKfQoKdm9sLnBsb3RzW1sxXV0gKyB2b2wucGxvdHNbWzJdXSArdm9sLnBsb3RzW1szXV0gKyBwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICdBJykrcGxvdF9sYXlvdXQobmNvbCA9IDIpCgoKCmBgYAoKCiMjIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9uIGZpbmFsIGRheSB1c2luZyBERUdzCgogIFdlIHdhbnQgdG8gY2hlY2sgd2hldGhlciBpbmZvcm1hdGlvbiBhYm91dCBkaWV0IGdyb3VwcyBwcmlvciB0byBzd2l0Y2ggY2FuIGJlIHJldHJpZXZlZCBhdCBkYXkgMjAgLSBpLmUgMTMgZGF5cyBhZnRlciB0aGUgc3dpdGNoLiBGb3IgdGhpcywgd2UgdXNlIGRpZXQgc2lnbmF0dXJlIGdlbmVzIGlkZW50aWZpZWQgYmVmb3JlIHRoZSBzd2l0Y2ggKERFR3MpIHRvIGhpZXJhcmNoaWNhbGx5IGNsdXN0ZXIgdGhlIGdyb3Vwcy4gV2UgY2FuIGFsbW9zdCBwZXJmZWN0bHkgY2xhc3NpZnkgZ3JvdXBzIHVzaW5nIFJlY29yZC1zZXEgZGF0YSwgd2hpbGUgZm9yIFJOQS1zZXEsIHRoZSBncm91cHMgY29udmVyZ2UuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD0yLjUsIGZpZy53aWR0aD0zfQpyZWMyZF9kYXkyMDwtcmVjMmRbcmVjMmQkRGF5PT0yMCxdCnJlYzJfZGF5MjA8LXJlYzJbLHJvd25hbWVzKHJlYzJkX2RheTIwKV0KcmVjMl9kYXkyMF90ZjwtcmVjb1Jkc2VxLnRyYW5zZm9ybShyZWMyX2RheTIwLCByZWMyZF9kYXkyMCkKCmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCnJpYm9zb21hbDwtYyhncmVwKCJycnMiLCByZWMyLmdsb2JhbC5ERUckZ2VuZUlEKSwgZ3JlcCgicnJsIiwgcmVjMi5nbG9iYWwuREVHJGdlbmVJRCkpCmlmKGxlbmd0aChyaWJvc29tYWwpPjApewogIHJlYzIuZ2xvYmFsLkRFRzwtcmVjMi5nbG9iYWwuREVHWy1yaWJvc29tYWwsXQp9CnJlYzIuZ2VuZVNob3J0TGlzdDwtdW5pcXVlKGMocmVjMi5nbG9iYWwuREVHW3doaWNoKHJlYzIuZ2xvYmFsLkRFRyRsb2cyRm9sZENoYW5nZS5tYXhfRkM+Mi41KSwgMV1bMToxMF0sIHJlYzIuZ2xvYmFsLkRFR1t3aGljaChyZWMyLmdsb2JhbC5ERUckbG9nMkZvbGRDaGFuZ2UubWF4X1NDPjIuNSksIDFdWzE6MTBdLHJlYzIuZ2xvYmFsLkRFR1t3aGljaChyZWMyLmdsb2JhbC5ERUckbG9nMkZvbGRDaGFuZ2UubWF4X1NDPCgtMi41KSZyZWMyLmdsb2JhbC5ERUckbG9nMkZvbGRDaGFuZ2UubWF4X0ZDPCgtMi41KSksIDFdWzE6MTBdKSkKZGhlYXRtYXA8LWFzLmRhdGEuZnJhbWUodChhcHBseShyZWMyX2RheTIwX3RmW3JlYzIuZ2VuZVNob3J0TGlzdCxdLCAxLCB6c2NvcmVzdGFuZGFyZGl6ZSkpKQpoZWF0bWFwLnJlYzI8LXBoZWF0bWFwKGRoZWF0bWFwLCBhbm5vdGF0aW9uX2NvbCA9IHJlYzJkX2RheTIwWywxLCBkcm9wPUZBTFNFXSwgYW5ub3RhdGlvbl9jb2xvcnM9Y29sb3VyX2NvZGUsIHRyZWVoZWlnaHRfcm93PTAsIGZvbnRzaXplID0gNSwgZm9udHNpemVfcm93ID0gNSwgZm9udHNpemVfY29sID0gNSwgY2x1c3Rlcl9yb3dzID0gRkFMU0UsIHNob3dfY29sbmFtZXMgPSBGQUxTRSxjb2xvciA9IGNvbHMsZm9udHNpemVfbnVtYmVyPTUsIG1haW49J0hpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIFJlY29yZC1zZXEgZGF0YSBvbiBkYXkgMjAgYmFzZWQgb24gZGlldCBzaWduYXR1cmUgZ2VuZXMnKQpoZWF0bWFwLmdlbmVsaXN0PC1yZWMyX2RheTIwX3RmW3JlYzIuZ2VuZVNob3J0TGlzdCxdCmNvbG5hbWVzKGhlYXRtYXAuZ2VuZWxpc3QpPC1hcy5jaGFyYWN0ZXIocmVjMmRfZGF5MjAkRGlldCkKCnJuYTJkX2RheTIwPC1ybmEyZFtybmEyZCREYXk9PTIwLF0Kcm5hMl9kYXkyMDwtcm5hMlsscm93bmFtZXMocm5hMmRfZGF5MjApXQpybmEyX2RheTIwX3RmPC1yZWNvUmRzZXEudHJhbnNmb3JtKHJuYTJfZGF5MjAsIHJuYTJkX2RheTIwKQpybmEyLmdlbmVTaG9ydExpc3Q8LXVuaXF1ZShjKHJuYTIuREVHW3JuYTIuREVHJGxvZzJGb2xkQ2hhbmdlLkZhdF92c19DaG93PjIuNSwgMV1bMToxMF0sIHJuYTIuREVHW3JuYTIuREVHJGxvZzJGb2xkQ2hhbmdlLlN0YXJjaF92c19DaG93PjIuNSwgMV1bMToxMF0sIHJuYTIuREVHW3doaWNoKHJvd01lYW5zKHJuYTIuREVHWywzOjRdKTwoLTIuNSkpLCAxXVsxOjEwXSkpCmNvbHM8LSBjb2xvclJhbXBQYWxldHRlKGMoImRvZGdlcmJsdWU0IiwgIndoaXRlIiwidmlvbGV0cmVkNCIpKSgyNTYpCmRoZWF0bWFwPC1hcy5kYXRhLmZyYW1lKHQoYXBwbHkocm5hMl9kYXkyMF90ZltybmEyLmdlbmVTaG9ydExpc3QsXSwgMSwgenNjb3Jlc3RhbmRhcmRpemUpKSkKaGVhdG1hcC5ybmEyPC1waGVhdG1hcChkaGVhdG1hcCwgYW5ub3RhdGlvbl9jb2wgPSBybmEyZF9kYXkyMFssMSwgZHJvcD1GQUxTRV0sIGFubm90YXRpb25fY29sb3JzPWNvbG91cl9jb2RlLCB0cmVlaGVpZ2h0X3Jvdz0wLCBmb250c2l6ZSA9IDUsIGZvbnRzaXplX3JvdyA9IDUsIGZvbnRzaXplX2NvbCA9IDUsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBzaG93X2NvbG5hbWVzID0gRkFMU0UsIGNvbG9yID0gY29scyxmb250c2l6ZV9udW1iZXI9NSwgbWFpbj0nSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgUk5BLXNlcSBkYXRhIG9uIGRheSAyMCBiYXNlZCBvbiBkaWV0IHNpZ25hdHVyZSBnZW5lcycpCmhlYXRtYXAuZ2VuZWxpc3Q8LXJuYTJfZGF5MjBfdGZbcm5hMi5nZW5lU2hvcnRMaXN0LF0KY29sbmFtZXMoaGVhdG1hcC5nZW5lbGlzdCk8LWFzLmNoYXJhY3RlcihybmEyZF9kYXkyMCREaWV0KQpgYGAKCiMjIEVjb2N5YyBhbmFseXNpczoKCiAgV2UgY3JlYXRlIGVucmljaG1lbnQgcGxvdHMgZm9yIHRvcCBkaWZmZXJlbnRpYWxseSByZWd1bGF0ZWQgcGF0aHdheXMgaWRlbnRpZmllZCBieSBFY29jeWMgdXNpbmcgdGhlIHBhaXJ3aXNlIERFRyBsaXN0cyBnZW5lcmF0ZWQgaGVyZS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KQ2hvd1hTdGFyY2gucGF0aHdheTwtYXMuZGF0YS5mcmFtZShyZWFkLnRhYmxlKCJkYXRhL0Nob3cuU3RhcmNoLnBhdGh3YXkudHh0IiwgaGVhZGVyPVRSVUUsIHNlcCA9ICdcdCcpKQpDaG93WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0Nob3dYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5DaG93J108LWxvZzEwKENob3dYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hTdGFyY2gucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkNob3cnXSkqKC0xKQpDaG93WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzW0Nob3dYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5TdGFyY2gnXTwtbG9nMTAoQ2hvd1hTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tDaG93WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uU3RhcmNoJ10pCkNob3dYU3RhcmNoLnBhdGh3YXk8LUNob3dYU3RhcmNoLnBhdGh3YXlbb3JkZXIoQ2hvd1hTdGFyY2gucGF0aHdheSRwLnZhbHVlcyksXQpDaG93WFN0YXJjaC5wYXRod2F5LnBsb3Q8LWdncGxvdChDaG93WFN0YXJjaC5wYXRod2F5LCBhZXMoeD1QYXRod2F5LCB5PXAudmFsdWVzLCBzaXplPW51bWJlci5vZi5nZW5lcywgY29sb3VyPWdyb3VwICkpK2dlb21fcG9pbnQoKStjb29yZF9mbGlwKCkrdGhlbWVfcHViK3NjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMSwzKSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gKC0xLjMwMTAzKSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMzAxMDMsIHNpemU9MC4yNCkreGxhYigiIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKSsgbGFicyhzaXplID0gIkdlbmVzIGRldGVjdGVkIikrc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHM9Q2hvd1hTdGFyY2gucGF0aHdheSRQYXRod2F5KStzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhhcy5jaGFyYWN0ZXIoY29sb3VyX2NvZGUkRGlldFtjKDEsMyldKSkpCgpDaG93WFN0YXJjaC5wYXRod2F5LnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKCkNob3dYRmF0LnBhdGh3YXk8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS9DaG93LkZhdC5wYXRod2F5LnR4dCIsIGhlYWRlcj1UUlVFLCBzZXAgPSAnXHQnKSkKQ2hvd1hGYXQucGF0aHdheSRwLnZhbHVlc1tDaG93WEZhdC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uQ2hvdyddPC1sb2cxMChDaG93WEZhdC5wYXRod2F5JHAudmFsdWVzW0Nob3dYRmF0LnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5DaG93J10pKigtMSkKQ2hvd1hGYXQucGF0aHdheSRwLnZhbHVlc1tDaG93WEZhdC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uRmF0J108LWxvZzEwKENob3dYRmF0LnBhdGh3YXkkcC52YWx1ZXNbQ2hvd1hGYXQucGF0aHdheSRncm91cD09J2VucmljaGVkLmluLkZhdCddKQpDaG93WEZhdC5wYXRod2F5PC1DaG93WEZhdC5wYXRod2F5W29yZGVyKENob3dYRmF0LnBhdGh3YXkkcC52YWx1ZXMpLF0KQ2hvd1hGYXQucGF0aHdheS5wbG90PC1nZ3Bsb3QoQ2hvd1hGYXQucGF0aHdheSwgYWVzKHg9UGF0aHdheSwgeT1wLnZhbHVlcywgc2l6ZT1udW1iZXIub2YuZ2VuZXMsIGNvbG91cj1ncm91cCApKStnZW9tX3BvaW50KCkrY29vcmRfZmxpcCgpK3RoZW1lX3B1YitzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsMykpK2dlb21faGxpbmUoeWludGVyY2VwdCA9ICgtMS4zMDEwMyksIHNpemU9MC4yNCkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLjMwMTAzLCBzaXplPTAuMjQpK3hsYWIoIiIpK3lsYWIoIi1sb2cxMCBwLWFkanVzdGVkIHZhbHVlIikrIGxhYnMoc2l6ZSA9ICJHZW5lcyBkZXRlY3RlZCIpK3NjYWxlX3hfZGlzY3JldGUobGltaXRzPUNob3dYRmF0LnBhdGh3YXkkUGF0aHdheSkrc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoYXMuY2hhcmFjdGVyKGNvbG91cl9jb2RlJERpZXRbYygxLDIpXSkpKQoKQ2hvd1hGYXQucGF0aHdheS5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgpGYXRYU3RhcmNoLnBhdGh3YXk8LWFzLmRhdGEuZnJhbWUocmVhZC50YWJsZSgiZGF0YS9GYXQuU3RhcmNoLnBhdGh3YXkudHh0IiwgaGVhZGVyPVRSVUUsIHNlcCA9ICdcdCcpKQpGYXRYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbRmF0WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uRmF0J108LWxvZzEwKEZhdFhTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tGYXRYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5GYXQnXSkqKC0xKQpGYXRYU3RhcmNoLnBhdGh3YXkkcC52YWx1ZXNbRmF0WFN0YXJjaC5wYXRod2F5JGdyb3VwPT0nZW5yaWNoZWQuaW4uU3RhcmNoJ108LWxvZzEwKEZhdFhTdGFyY2gucGF0aHdheSRwLnZhbHVlc1tGYXRYU3RhcmNoLnBhdGh3YXkkZ3JvdXA9PSdlbnJpY2hlZC5pbi5TdGFyY2gnXSkKRmF0WFN0YXJjaC5wYXRod2F5PC1GYXRYU3RhcmNoLnBhdGh3YXlbb3JkZXIoRmF0WFN0YXJjaC5wYXRod2F5JHAudmFsdWVzKSxdCkZhdFhTdGFyY2gucGF0aHdheS5wbG90PC1nZ3Bsb3QoRmF0WFN0YXJjaC5wYXRod2F5LCBhZXMoeD1QYXRod2F5LCB5PXAudmFsdWVzLCBzaXplPW51bWJlci5vZi5nZW5lcywgY29sb3VyPWdyb3VwICkpK2dlb21fcG9pbnQoKStjb29yZF9mbGlwKCkrdGhlbWVfcHViK3NjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMSwzKSkrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gKC0xLjMwMTAzKSwgc2l6ZT0wLjI0KStnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK2dlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMzAxMDMsIHNpemU9MC4yNCkreGxhYigiIikreWxhYigiLWxvZzEwIHAtYWRqdXN0ZWQgdmFsdWUiKSsgbGFicyhzaXplID0gIkdlbmVzIGRldGVjdGVkIikrc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHM9RmF0WFN0YXJjaC5wYXRod2F5JFBhdGh3YXkpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGFzLmNoYXJhY3Rlcihjb2xvdXJfY29kZSREaWV0W2MoMiwzKV0pKSkKCkZhdFhTdGFyY2gucGF0aHdheS5wbG90K3Bsb3RfYW5ub3RhdGlvbigpCgoKYGBgCiMgQ2hlY2tpbmcgcmVwcm9kdWNpYmlsaXR5IG9mIGNsYXNzaWZpZXIgZ2VuZXMgKERFR3MpCgogV2Ugd2FudCB0byBjb25maXJtIHRoYXQgdGhlcmUgaXMgYW4gb3ZlcmxhcCBhbW9uZyB0aGUgREVHcyAgaWRlbnRpZmllZCBpbiB0aGUgdHdvIGV4cGVyaW1lbnRhbCByZXBsaWNhdGVzLCBhbmQgdGhlIGRpcmVjdGlvbiBvZiBkaWZmZXJlbnRpYWwgcmVndWxhdGlvbiBpcyBjb25zaXN0ZW50LiBXZSB1c2UgdGhlIGdlbmVzIHRoYXQgYXJlIHVwcmVndWxhdGVkL2Rvd25yZWd1bGF0ZWQgaW4gdGhlIENob3cgZ3JvdXAgY29tcGFyZWQgdG8gdGhlIFN0YXJjaCBncm91cCBvbiBkYXkgNy4KIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0ID0gMywgZmlnLndpZHRoID0gMywgZmlnLmFsaWduID0gImNlbnRlciJ9CmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzEuZGUudmFsc1tbMl1dLCBwID0gMC4xKQplZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMxLmVkLnZhbHNbWzJdXSwgcCA9IDAuMSkKcmVjMS5DaG93LnYuU3RhcmNoLkRFRzwtZGF0YS5mcmFtZShyb3cubmFtZXMgPSBpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIGxvZzJGQz1yZWMxLmRlLnZhbHNbWzJdXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSw0XSkKCmRlc2VxLmdlbmVzPC1yZWNvUmRzZXEuZmlsdGVyREVHKHJlYzIuZGUudmFsc1tbMl1dLCBwID0gMC4xKQplZGdlci5nZW5lczwtcmVjb1Jkc2VxLmZpbHRlckRFRyhyZWMyLmVkLnZhbHNbWzJdXSwgcCA9IDAuMSkKcmVjMi5DaG93LnYuU3RhcmNoLkRFRzwtZGF0YS5mcmFtZShyb3cubmFtZXMgPSBpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSxnZW5lSUQ9aW50ZXJzZWN0KGRlc2VxLmdlbmVzLCBlZGdlci5nZW5lcyksIGxvZzJGQz1yZWMyLmRlLnZhbHNbWzJdXVtpbnRlcnNlY3QoZGVzZXEuZ2VuZXMsIGVkZ2VyLmdlbmVzKSw0XSkKCnBsb3QoZXVsZXIobGlzdChyZWMxID0gcmVjMS5DaG93LnYuU3RhcmNoLkRFRyRnZW5lSUQsIHJlYzIgPSByZWMyLkNob3cudi5TdGFyY2guREVHJGdlbmVJRCkpICwgcXVhbnRpdGllcz1UUlVFKQoKYGBgCiAgRmluYWxseSwgd2UgcGxvdCBhIGNvcnJlbGF0aW9uIHBsb3QgYmFzZWQgb24gdGhlIGxvZzJGQyBkZXRlY3RlZCBmb3IgREVHcyBmb3IgdGhlIHR3byBleHBlcmltZW50cywgYW5kIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgREVHcyByZWd1bGF0ZWQgaW4gYSBzaW1pbGFyIGRpcmVjdGlvbi4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9Mi41LCBmaWcud2lkdGg9M30KIERFRy5jb21wYXJlPC1kYXRhLmZyYW1lKGdlbmVJRD1pbnRlcnNlY3QocmVjMS5DaG93LnYuU3RhcmNoLkRFRyRnZW5lSUQsIHJlYzIuQ2hvdy52LlN0YXJjaC5ERUckZ2VuZUlEKSkKREVHLmNvbXBhcmUkZ2VuZUlEPC1hcy5jaGFyYWN0ZXIoREVHLmNvbXBhcmUkZ2VuZUlEKQpERUcuY29tcGFyZSRyZWMxX2xvZzJGQzwtcmVjMS5DaG93LnYuU3RhcmNoLkRFR1tERUcuY29tcGFyZSRnZW5lSUQsMl0KREVHLmNvbXBhcmUkcmVjMl9sb2cyRkM8LXJlYzIuQ2hvdy52LlN0YXJjaC5ERUdbREVHLmNvbXBhcmUkZ2VuZUlELDJdCkRFRy5jb21wYXJlPC1ERUcuY29tcGFyZVtjb21wbGV0ZS5jYXNlcyhERUcuY29tcGFyZSksIF0KcjI8LXJvdW5kKGNvcihERUcuY29tcGFyZSRyZWMxX2xvZzJGQywgREVHLmNvbXBhcmUkcmVjMl9sb2cyRkMpXjIsMikKbjwtcm91bmQobGVuZ3RoKHdoaWNoKERFRy5jb21wYXJlJHJlYzFfbG9nMkZDKkRFRy5jb21wYXJlJHJlYzJfbG9nMkZDPjApKSoxMDAvbGVuZ3RoKERFRy5jb21wYXJlJGdlbmVJRCkpCkRFRy5zY2F0dGVycGxvdDwtZ2dwbG90KERFRy5jb21wYXJlLCBhZXMoeT1yZWMxX2xvZzJGQywgeD1yZWMyX2xvZzJGQykpK2dlb21fcG9pbnQoc2l6ZT0wLjQ4LCBhZXMoY29sb3VyPSdncmF5MTAnKSkrZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJywgc2UgPSBGQUxTRSwgc2l6ZT0wLjQ4KSt0aGVtZV9wdWIrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgc2l6ZT0wLjI0KStnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBzaXplPTAuMjQpK3hsYWIoImxvZzIgZm9sZCBjaGFuZ2Ugb2YgREVHcyBkZXRlY3RlZCBpbiB0cmFuc2llbnQgZGlldCAyIikreWxhYigiY29ycmVzcG9uZGluZyBsb2cyIGZvbGQgY2hhbmdlIGluIHRyYW5zaWVudCBkaWV0IDEiKSthbm5vdGF0ZSgidGV4dCIsICB4PUluZiwgeSA9IEluZiwgbGFiZWwgPSBwYXN0ZTAoIlJeMiA9Iixhcy5jaGFyYWN0ZXIocjIpLCAiIFxuIERFR3MgcmVndWxhdGVkIGluIHRoZSBzYW1lIGRpcmVjdGlvbiA9ICIsYXMuY2hhcmFjdGVyKG4pLCAiJSIpLCB2anVzdD0xLCBoanVzdD0xLCBzaXplPTMpK3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdncmF5MTAnKSwgZ3VpZGU9J25vbmUnKStnZ3RpdGxlKCJDb3JyZWxhdGlvbiBvZiBvdmVybGFwcGluZyBERUdzIGRldGVjdGVkIGluIGJvdGggZXhwZXJpbWVudHMiKQpERUcuc2NhdHRlcnBsb3QrcGxvdF9hbm5vdGF0aW9uKCkKYGBgCiMgSW5mb3JtYXRpb24gYWJvdXQgUiBzZXNzaW9uCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNlc3Npb25JbmZvKCkKYGBg